webpack-best-practice-最佳實踐-部署生產

weixin_33806914發表於2015-12-06

tip

webpack的入門篇可以看我的這一片博文。
《如何使用webpack—webpack-howto》.

前言

最近一段時間在專案中使用了webpack和React來開發,總之來說也是遇到了許多坑,webpack畢竟還是比較新的技術,而且也很難有一個很好的構建案例來適應所有的專案,總之,在看了許多專案demo和官方文件以及官方推薦的tutorials之後,也算是自己總結出的一套最佳實踐吧。

程式碼

程式碼可以在我的Github上。
可以戳這裡~~

package.json 命令配置

既然是需要用到的是實際專案的構建,那麼必然就要考慮開發環境和生產環境下的配置項了:

// package.json
{
  // ...
  "scripts": {
    "build": "webpack --progress --colors --watch",
    "watch": "webpack-dev-server --hot --progress --colors",
    "dist": "NODE_ENV=production webpack --progress --colors"
  },
  // ...
}

可以在目錄下執行

npm run build
npm run watch
npm run dist

解釋一下:

  • build 是在我們開發環境下執行的構建命令;

  • watch 也是在開發環境下執行,但是加了webpack最強大的功能--搭建靜態伺服器和熱插拔功能(這個在後面介紹;

  • dist 是專案在要部署到生產環境時打包釋出。

dist 裡面的NODE_ENV=production是宣告瞭當前執行的環境是production-生產環境

後面跟著幾個命令:

  • --colors 輸出的結果帶彩色

  • --progress 輸出進度顯示

  • --watch 動態實時監測依賴檔案變化並且更新

  • --hot 是熱插拔

  • --display-error-details 錯誤的時候顯示更多詳細錯誤資訊

  • --display-modules 預設情況下 node_modules 下的模組會被隱藏,加上這個引數可以顯示這些被隱藏的模組

  • -w 動態實時監測依賴檔案變化並且更新

  • -d 提供sorcemap

  • -p 對打包檔案進行壓縮

目錄結構

現在前端模組化的趨勢導致目錄結構也發生了很大的改變和爭議,這只是我自己用到的一種形式,可以參考。

.
├── app                 #開發目錄
|   ├──assets           #存放靜態資源
|   |   ├──datas        #存放資料 json 檔案
|   |   ├──images       #存放圖片資原始檔
|   |   └──styles       #存放全域性sass變數檔案和reset檔案
|   ├──lib
|   |   ├──components   #存放資料 模組元件 檔案
|   |   |   └──Header
|   |   |       ├──Header.jsx
|   |   |       └──Header.scss
|   |   |       
|   |   └──utils        #存放utils工具函式檔案
|   |
|   └──views
|       ├──Index        #入口檔案
|       |   ├──Index.html #html檔案
|       |   ├──Index.jsx
|       |   └──Index.scss
|       └──Index2
├── dist                #釋出目錄
├── node_modules        #包資料夾
├── .gitignore     
├── .jshintrc      
├── webpack.config.js   #webpack配置檔案
└── package.json

具體可以到Github上看demo。

webpack.config.js

引入包

var webpack = require('webpack');
var path = require('path');
var fs = require('fs');

這個毋庸置疑吧。

判斷是否是在當前生產環境

定義函式判斷是否是在當前生產環境,這個很重要,一位開發環境和生產環境配置上有一些區別

var isProduction = function () {
  return process.env.NODE_ENV === 'production';
};

宣告資料夾

// 定義輸出資料夾
var outputDir = './dist';
// 定義開發資料夾
var entryPath = './app/views';

定義外掛

var plugins = [
  new webpack.optimize.CommonsChunkPlugin({
    name: 'commons',
    filename: 'js/commons.js',
  }),
  new webpack.ProvidePlugin({
    React: 'react',
    ReactDOM: 'react-dom',
    reqwest: 'reqwest',
  }),
];
if( isProduction() ) {
  plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      test: /(\.jsx|\.js)$/,
      compress: {
        warnings: false
      },
    })
  );
}
  1. CommonsChunkPlugin 外掛可以打包所有檔案的共用部分生產一個commons.js檔案。

  2. ProvidePlugin 外掛可以定義一個共用的入口,比如下面加的 React ,他會在每個檔案自動require了react,所以你在檔案中不需要 require('react'),也可以使用 React。

  3. 如果是在生產環境下,則加入外掛 UglifyJsPlugin ,執行程式碼壓縮,並且去除 warnings。

自動遍歷多檔案入口

var entris = fs.readdirSync(entryPath).reduce(function (o, filename) {
    !/\./.test(filename) &&
    (o[filename] = './' + path.join(entryPath, filename, filename + '.jsx'));
    return o;
  }, {}
);

函式會自動遍歷開發的入口資料夾下面的檔案,然後一一生產入口並且返回一個物件--入口。

如果在這一步不需要多頁面多入口

那麼可以使用html-webpack-plugin外掛,它可以自動為入口生成一個html檔案,配置如下:

var HtmlWebpackPlugin = require('html-webpack-plugin');
plugins.push(new HtmlWebpackPlugin({
  title: 'index',
  filename: outputDir+'/index.html',  #生成html的位置
  inject: 'body',                     #插入script在body標籤裡
}));

entry 就可以自定義一個入口就夠了

config的具體配置

var config = {
  target: 'web',
  cache: true,
  entry: entris,
  output: {
    path: outputDir,
    filename: 'js/[name].bundle.js',
    publicPath: isProduction()? 'http://******' : 'http://localhost:3000',
  },
  module: {
    loaders: [
      {
        test: /(\.jsx|\.js)$/,
        loaders: ['babel?presets[]=es2015&presets[]=react'],
        exclude: /node_modules/
      },
      {
        test: /\.scss$/,
        loaders: ['style', 'css?root='+__dirname, 'resolve-url', 'sass']
      },
      {
        test: /\.json$/,
        loader: 'json',
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/,
        loader: 'url?limit=1024&name=img/[name].[ext]'
      },
      {
        test: /\.(woff2?|otf|eot|svg|ttf)$/i,
        loader: 'url?name=fonts/[name].[ext]'
      },
      {
        test: /\.html$/,
        loader: 'file?name=views/[name].[ext]'
      },
    ]
  },
  plugins: plugins,
  resolve: {
    extensions: ['', '.js', 'jsx'],
  },
  devtool: isProduction()?null:'source-map',
};

這裡來一一說明:

對於output

path和filename都不用多說了,path是生成檔案的存放目錄,filename是檔名,當然可以在前面加上目錄位置。
這裡提醒一下,filename 的相對路徑就是 path了,並且下面 靜態檔案生成的filename也是相對於這裡的path的,比如 image 和 html。
publicPath 的話是打包的時候生成的檔案連結,比如 圖片 資源,
如果是在生產環境當然是用伺服器地址,如果是開發環境就是用本地靜態伺服器的地址。

module loaders 打包載入的處理器

可以不用夾 loader了 比如 原來 url-loader 現在 url

js/jsx

{
  test: /(\.jsx|\.js)$/,
  loaders: ['babel?presets[]=es2015&presets[]=react'],
  exclude: /node_modules/
},

對於js檔案和jsx檔案用了babel來處理,這裡注意一下,最新版本的babel吧es2015和react的處理分開了,所有要這麼寫。

處理scss檔案

{
  test: /\.scss$/,
  loaders: ['style', 'css?root='+__dirname, 'resolve-url', 'sass']
},

這裡用了sass、css、style的loader這不用多說了。
那麼root和resolve-url是怎麼回事呢,root是定義了scss檔案裡面宣告的url地址是相對於根目錄的,然後resolve-url回去相對解析這個路徑,而不用require去獲取,比如

background: url('./assets/images/webpack.png');

這樣就可以載入到./assets/images/webpack.png這個檔案,而不用使用相對路徑和require

處理json檔案

{
  test: /\.json$/,
  loader: 'json',
},

對於json檔案,可以自動請求該模組並且打包。

處理 圖片 字型 資原始檔

{
  test: /\.(jpe?g|png|gif|svg)$/,
  loader: 'url?limit=1024&name=img/[name].[ext]'
},
{
  test: /\.(woff2?|otf|eot|svg|ttf)$/i,
  loader: 'url?name=fonts/[name].[ext]'
},

這裡使用了 url 這個loader,但是url依賴 file-loader,它是對file-loader的二次封裝。
在請求圖片的時候如果檔案大小小於 1024k ,使用內聯 base64 URLs,否則會自動匯入到name所宣告的目錄,這裡是相對之前宣告的 outputDir 路徑。
字型資源也是一樣。

處理html檔案

{
  test: /\.html$/,
  loader: 'file?name=views/[name].[ext]'
},

在多頁面的專案中需要,可以自動吧html檔案匯入到指定的生產資料夾下。

resolve

resolve: {
  extensions: ['', '.js', 'jsx'],
},

是可以忽略的檔案字尾名,比如可以直接require('Header');而不用加.jsx。

devtool

devtool: isProduction()?null:'source-map',

規定了在開發環境下才使用 source-map。

疑問

目前為止,對於多頁面專案還是沒有找到一個很好的方案去構建自動化。

原文可以看我的部落格 webpack-best-practice-最佳實踐-部署生產;

相關文章