【譯】webpack之謎

【注】本文原發自此處,轉載請註明出處。css

本文譯自【Webpack-The Confusing Parts】原文html

本文已同步發表在個人博客node

前言

webpack是當前最受歡迎的模塊管理器(module bundler),對於使用React開發的項目來講堪稱神器。固然,對於使用其餘框架,好比Angular或者Backbone等的開發者來講,webpack也是種很好的工具。react

第一次配置webpack.config.js時,有不少地方使我很困惑。在使用了webpack一段時間後,我認識到正是這些地方讓webpack如此強大和迷人。jquery

webpack核心理念

  • 一切都是模塊。—— 在webpack中,不只js文件能夠做爲一個模塊,其餘文件(cssimageshtml)均可以做爲模塊。這就是說,你能夠在其餘文件中加載這些模塊,require('myJSfile.js'),或者require('myCSSfile.css')。這意味着咱們將任意文件拆分紅便於管理的小文件,而後經過在其餘文件中加載這些小文件來達到重複利用的目的。
  • 按需加載(Load only "what" you need and "when" you need)—— 通常狀況下打包工具會將咱們全部的模塊打包生成一個最終的文件bundle.js。可是在實際應用中,bundle.js一般會很大(10M~15M),須要很長時間才能加載完成。webpack提供了多種code splitting的方法,會生成過個打包後的文件,且支持按需加載。這樣咱們只有在須要用到某個模塊的時候纔會異步加載該模塊。

如今咱們來看下這些使人困惑的部分。webpack

開發環境和生產環境(Development Vs Production)

首先要明確的一點是,webpack有不少特性,有些只在開發環境使用,還有些只在生產環境使用,固然還有在生產環境和開發環境均可以使用的。以下圖所示:
圖片描述es6

因此一般咱們會有兩個 config文件,以針對開發環境和生產環境做不一樣配置。

package.json作以下配置:web

"scripts": {
  //npm run build to build production bundles
  "build": "webpack --config webpack.config.prod.js",
  //npm run dev to generate development bundles and run dev. server
  "dev": "webpack-dev-server"
 }

webpack CLI Vs webpack-dev-server

須要知道webpack提供了兩個接口npm

  1. webpack命令行工具(webpack CLI tool) —— 默認使用這種方式,無需單獨安裝,被集成在webpack中。
  2. webpack-dev-server —— node.js 服務器,須要單獨安裝

Webpack CLI (適用於生產環境構建)

能夠經過命令行添加參數,也能夠經過配置文件(默認爲webpack.config.js),webpack打包時會讀取這些配置。json

最初學習 webpack時你可能用的就是命令行方式,以後大部分使用命令行的場景爲生產環境打包。

使用方法

方法 1: 
//全局安裝
npm install webpack --g
//在命令行使用
$ webpack //<--Generates bundle using webpack.config.js

方法 2 :
//本地安裝並保存在package.json中
npm install webpack --save
//在scripts中添加
"scripts": {
 "build": "webpack --config webpack.config.prod.js -p",
 ...
 }
//按如下方式運行
"npm run build"

webpack-dev-server(適用於開發環境構建)

webpack-dev-server是一個基於Expressnode服務器,默認使用8080端口。這個方式的優勢是它提供了瀏覽器熱加載(Hot Module Replacement)。
使用方法

方法一:
//全局安裝
npm install webpack-dev-server -g
//在命令行使用
$ webpack-dev-server --inline --hot

方法二:
//添加到package.json中
"script": {
     "start": "webpack-dev-server --inline --hot",
     ...
}
//在命令行使用
$ npm start

在瀏覽器中打開
http://localhost:8080/

webpackwebpack-dev-server選項

須要注意的一點是,像inlinehot這些選項,只有webpack-dev-server有;而另外一些好比hide-modules是單獨爲webpack命令行方式提供的選項。

webpack-dev-server參數

webpack-dev-server提供參數有兩種方式。

  1. 經過webpack.config.js中的devServer
  2. 經過CLI選項

使用方法

//經過CLI
$ webpack-dev-server --hot --inline

//經過webpack.config.js
devServer: {
    inline: true,
    hot: true
}
我發現經過 devServer設置的配置項( hot: true, inline: true)有時不起做用。因此我更喜歡使用 CLI的方式,在 package.json中添加以下代碼:
package.json
{
    "script": {
        "start": "webpack-dev-server --hot --inline"
    }
}
注意 不要同時設置 devServerhot: trueCLI--hot

"hot" Vs "inline"

inline模式會觸發頁面的動態重載(live reloading);hot模式會觸發頁面的熱加載(hot Module Replacement),這種模式只重載頁面中變化了的部分。若是同時設置了inlinehotwebpack-dev-server會先嚐試HMR,若是HMR失敗了,則重載整個頁面。

//當代碼發生變化時,如下3種方式都會從新打包,可是:

//1. 不會重載頁面
$ webpack-dev-server

//2. 會重載整個頁面
$ webpack-dev-server --inline

//3. HMR, 若失敗則加載整個頁面
$ webpack-dev-server --inline --hot

entry(String Vs Array Vs Object)

entry指出了打包入口文件,支持字符串,數組對象三種形式。這三種形式有何區別呢?

若是爲單一入口文件,也就是說入口文件只有一個,那這三種方式會獲得相同的結果。

entryArray

如有多個入口文件,且彼此獨立,那麼可使用數組方式。好比入口文件爲a.jsb.js,使用數組方式會將b.js的內容追加到bundle.js的內容後。
一個很常見的場景就是在html文件加入統計代碼,好比googleAnalytics.js,就能夠用數組的方式告知webpack將其打包到bundle.js末尾,以下:
圖片描述

entryObject

這種方式主要針對多頁面應用(指包含多個html文件)。這種方式可使webpack根據這個對象一次就打包出多個文件。
以下這種配置能夠打包出兩個js文件:indexEntry.jsprofileEntry.js,能夠分別在index.htmlprofile.html中引入。

{
    entry: {
        "indexEntry": "./public/src/index.js",
        "profileEntry": "./public/src/profile.js"
    },
    output: {
        path: "/dist",
        filename: "[name].js"  //indexEntry.js & profileEntry.js
    }
}

使用方法

//profile.html
<script src="dist/profileEntry.js"></script>

//index.html
<script src="dist/indexEntry.js"></script>

注: outputname對應的是entry中的屬性名。

entry — 結合使用arrayobject

能夠在object內部再使用array方式。好比以下配置:

{
    entry: {
        "vendor": ['jquery', 'analytics.js', 'optimizely.js'],
        "index": "./public/src/index.js",
        "profile": "./public/src/profile.js"
    },
    
    output: {
        path: "/dist",
        filename: "[name].js"  //vendor.js, index.js & profile.js
    }
}

outputpathpublicPath

output設定了打包生成文件的路徑。它有兩個屬性pathpublicPath
path告知webpack將打包生成後的文件存儲於什麼路徑,好比咱們但願將文件打包到dist文件夾下,只需設置path/dist便可;publicPath用於在生產環境打包時更新文件(包括csshtml)中的url
以下配置:

//開發環境config
entry: __dirname + "/app/main.js",

output: {
    path: __dirname + "/public",
    
    //開發環境中不須要使用publicPath, 除非你的靜態資源好比圖片等沒有存儲在本地開發環境。
    //publicPath: "http://mycdn.com",
    filename: "bundle.js"
}

//生產環境config
entry: __dirname + "/app/main.js",

output: {
    path: __dirname + "/public",
    
    //publicPath: 一些插件(url-loader, file-loader, HtmlWebpackPlugin等)
    //在生成圖片,樣式表等的url路徑時會用到該配置
    //好比:
    //.image {
    //    background-image: url('./test/png');
    //}
    //按以下配置打包後會變成:
    //.image {
    //    background-image: url('http://mycdn.com/test.png');
    //}
    publicPath: "http://mycdn.com/",
    filename: "bundle.js"
}

舉個例子,在你的css文件中,用到了./test.png這個url去加載本地的圖片。可是在生產環境中,這張圖片test.png會存儲在cdn服務器上。這樣若是仍是用./test.png就會訪問不到該圖片,必須把文件中全部的url手動改爲cdn的路徑才能在生產環境使用。

webpack爲咱們提供的publicPath這個屬性使咱們能夠很方便地處理這類問題。只須要將publicPath設置爲生產環境的路徑,這些識別publicPath的插件,好比url-loader,就會自動爲咱們處理好url。以下圖所示:

圖片描述

//開發環境,server和image都在本地
.image {
    background-image: url('./test.png');
}

//生產環境, server在HeroKu服務器上,而image在cdn上
.image {
    background-image: url('https://someCDN/test.png');
}

譯者注: publicPath還用於指定在使用webpack-dev-server時,如何訪問其暫存於內存中的打包後的文件。)

加載器和鏈式加載器(Loaders And Chaining Loaders

加載器是一些node模塊,能夠加載(load)或者引入(import)各類類型的文件使其轉化成瀏覽器支持的文件格式,包括js,css等等。
好比:可使用babel-loader將使用ES6寫的js文件轉換爲瀏覽器支持的ES5格式。以下:

module {
    loaders: [
        {
            test: /\.js$/,  //檢測js文件,若是是,則對其使用loader處理
            exclude: /node_modules/,  //不對node_modules下文件處理
            loader: 'babel'   //使用babel (對babel-loader的簡寫)
        }
    ]
}

鏈式加載(從右至左)

對同一種類型的文件能夠鏈式運用多個加載器。鏈式調用爲從右向左,經過!分割加載器。
舉例:咱們有一個css文件myCSSFile.css,咱們想將這個文件中的內容轉換成<style>CSS content</style>的形式插入到咱們的html頁面中。可使用兩個加載器css-loaderstyle-loader來達成以上目的:

modules: {
    loaders: [
        {
            test: /\.css$/,
            loader: 'style!css'  //style-loader!css-loader的簡寫
        }
    ]
}

以下展現了其原理:
圖片
一、webpack查找模塊中依賴的css文件。也就是說,webpack會檢查js文件中是否引用了myCSSFile.css。若是找到了依賴,webpack會先用css-loader對其進行處理。
二、css-loader會加載全部的css和這個css的依賴(好比@import otherCSS),並將css的內容處理爲JSON數據格式。而後將結果傳給style-loader進行處理。
三、style-loader會對接收到的json數據進行處理,並將其處理爲style標籤——<style>CSS contents</style>,而後插入到html頁面中。

加載器可配置

能夠向loaders傳遞各類參數進行配置。
在如下這個例子中,咱們對url-loader進行了配置:小於1024字節的圖片將會被轉爲爲base64格式,而大於1024字節的圖片仍是使用圖片url。有兩種方式進行配置:

//方式1 使用'?'
{
    test: /\.png$/,
    loader: "url-loader?limit=1024"
}

//方式2 使用'query'屬性
{
    test: /\.png$/,
    loader: "url-loader",
    query: {limit: 1024}
}

.babelrc文件

使用babel-loader的話,須要配置presets才能正確轉化,包括將es6轉換爲es5,將JSX轉爲js。能夠經過以下方式設置參數

module: {
    loaders: [
        {
            test: /\.jsx?$/,
            exclude: /(node_modules|bower_components)/,
            loader: 'babel',
            query: {
                presets: ['react', 'es2015']
            }
        }
    ]
}

可是在不少項目中babel的配置可能會比較大,因此能夠單獨在babel的配置文件.babelrc中配置。若是有.babelrcbabel-loader會自動加載該文件。
以下:

//webpack.config.js
module: {
    loaders: [
        {
            test: /\.jsx?$/,
            exclude: /(node_modules|bower_components)/,
            loader: 'babel'
        }
    ]
}

//.babelrc
{
    "presets": ["react", "es2015"]
}

插件(Plugins

插件是一些node模塊,能夠對生成的打包文件進行處理。
好比,uglifyJSPlugin插件能夠對打包後獲得的bundle.js進行壓縮處理,減少文件體積。
extract-text-webpack-plugin運用了css-loaderstyle-loader將全部的css統一處理並根據結果生成一個單獨的css文件(style.css),將文件連接插入到html文件中。

//webpack.config.js
//獲取全部的css文件,並將其內容整合,生成一個單獨的css文件'style.css'
var ETP = require('extract-text-webpack-plugin');

module: {
    loaders: [
        {
            test: /\.css$/,
            loader: ETP.extract('style-loader', 'css-loader')
        }
    ]
},

plugins: [
    new ExtractTextPlugin("style.css")
]

注:
若是你只是想使用內聯css樣式,在html頁面中加入style標籤,能夠只用cssstyle加載器。以下:

module: {
    loaders: [
        {
            test: /\.css$/,
            loader: 'style!css'
        }
    ]
}

加載器和插件(Loaders Vs Plugins)

能夠看到,加載器做用於單獨的文件,在bundle生成以前完成;
插件做用於bundlechunk,一般是在bundle生成過程的最後進行。一些插件好比commonsChunksPlugins甚至會影響bundle如何生成。(譯者注:該插件用於提取出各個模塊中引用的相同模塊,下篇文章code splitting中會詳細說明)

文件後綴處理(Resolving File Extensions)

不少webpack配置文件中都包含一個resolve extensions的屬性,其中包含一個空字符串。這個空字符串就是用於正確加載不含後綴的文件的。好比:require('./myJSFile')import myJSFile from './myJSFile'

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

注: 翻譯水平有限,若有問題還但願你們能不吝賜教,但願和你們共同進步。

(本文完)

相關文章
相關標籤/搜索