【30分鐘】吃透webpack,也許這一篇就夠了

本文webpack是在Mac平臺下基於官方最新版本v3.10,對於webpack@v2會有小的差別,待全文完成後會補充webpack@v2與v3版本之間的差別

使用webpack前的準備

一、初始化一個前端項目

爲了方便以後本身更好的使用這個webpack_starter,引入git的支持,一是能夠把一些通用的東西放在主分支,二是能夠把後面不一樣的配置支持能夠經過branch或者tag的方式分門別類。javascript

  • 在github初始化一個webpack_starter項目,以下圖所示,初始化.gitignore支持Node語言

clipboard.png

#clone項目到本地
git clone https://github.com/mpandar/webpack_starter.git
cd webpack_starter
  • 利用yarn初始化項目(固然一樣可使用npm,無太大差別,此處再也不補充npm使用方法)
初始化過程按照提示完成便可,惟一注意的是 entry point,這是webpack進行打包時的入口文件,默認是根目錄下的 index.js,不過一般狀況下,咱們的源碼都是在 src目錄下,因此修改成 src/index.js
> yarn init
yarn init v1.3.2
question name (webpack_starter): 
question version (1.0.0): 0.1.0
question description: a webpack start project
question entry point (index.js): src/index.js
question repository url (https://github.com/mpandar/webpack_starter.git): 
question author (mpandar <mshp_****@126.com>): 
question license (MIT): 
question private: 
success Saved package.json
✨  Done in 53.55s.

二、安裝webpack

單獨安裝與全局安裝對於webpack的使用並沒有太大差別,但推薦即便全局安裝之後,仍要在項目中進行單獨的安裝,方便項目移植。不然可能會致使全局安裝的webpack版本與項目中的配置文件可能存在不匹配。固然單獨安裝後,使用一些npm或yarn命令,它們會優先使用本地安裝的webpack
#全局安裝
yarn global add webpack
#單項目使用
yarn add webpack

三、初始化項目目錄

|---
    |--dist    //存放webpack打包後相關文件
    |--src     //存放項目源碼
       |--index.js 
    |--config  //項目相關的配置文件
       |--webpack.config.js   //webpack默認讀取項目根目錄下的webpack.config.js文件做爲配置信息,爲了規範化移入到config目錄下
    |--package.json

瞭解webpack配置文件webpack.config.js

固然,即便沒有配置文件,直接運行webpack命令,一樣能夠直接對js文件完成打包工做,這裏是一篇分析webpack打包後的代碼的文章:簡要分析webpack打包後代碼,其中用到的一個新命令npx,很簡單,介紹點這裏php

#直接打包
npx webpack src/index.js dist/bundle.js

爲了應對更靈活的使用場景,webpack支持配置文件,而且默認狀況下,在項目根目錄下,若是存在webpack.config.js文件,那麼webpack會主動讀取該文件做爲配置內容,不過Demo下,爲了更加符合咱們的目錄規範,咱們將config文件移到了config目錄下css

//當配置文件內容爲空時,運行該命令會提示`Configuration file found but no entry configured`
npx webpack src/index.js --config config/webpack.config.js

接下來,讓咱們看一下一個webpack配置文件,最簡單隻須要包含entry(定義入口文件)和output(定義打包輸出文件)這兩個部分:html

const path = require('path');
const base = path.join(__dirname, '..')

module.exports = {
  entry: path.resolve(base, 'src', 'index.js'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(base, 'dist')
  }
};
注:使用path模塊只是爲了代碼清晰,你徹底能夠不用,直接用 __dirname+'/../src'相似代碼拼接
//這樣就不須要在命令行定義輸入輸出文件啦
npx webpack --config config/webpack.config.js

除了entryoutput,webpack中最多見的就是moduleresolveplugins,大體結構以下:前端

module.exports = {
  entry: path.resolve(base, 'src', 'index.js'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(base, 'dist')
  },
  devtool: 'eval-source-map',
  devServer: {
    contentBase: path.resolve(base, 'dist'),
    historyApiFallback: true,
    inline: true,
    proxy: {
      "/api": "http://localhost:8000"
    }
  },
  module: {
    rules: [
    ]
  },
  resolve: {
  },
  plugins: [
  ]
};

固然瞭解webpack最好的地方永遠是官方文檔,傳送門,接下來,天然是在目前配置的基礎上,增添更多使人興奮的特性java

爲開發增長更多利器

生成Source Map,爲調試助力

開發離不開調試,但通過編碼後的代碼並不利於調試,很找到出錯的地方對應的你寫的代碼,而Source Maps就是來幫咱們解決這個問題的。
而webpack支持Source Maps僅僅是增長一行 devtool 配置選項,具體配置選項能夠看這裏的官方文檔,其中兩個選項 eval-source-mapsource-map 是比較經常使用的選項,前一個選項推薦僅僅用在開發環境,然後一個一般在一些第三方庫中,提供給開發者調試使用。固然對於任何上線項目,實際上都推薦使用*.min.js並不使用Source Map以加快網絡加載。node

module.exports = {
  entry: path.resolve(base, 'src', 'index.js'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(base, 'dist')
  },
  devtool: 'eval-source-map'
}

自動監控代碼更新,自動編譯,自動瀏覽器刷新

做爲開發者,總不但願把時間浪費在執行命令和刷新頁面上,webpack提供一個單獨的組件webpack-dev-server爲咱們提供一個基於nodejs的本地服務器、文件修改監控及編譯以及瀏覽器自動刷新等特性。webpack

首先是安裝:git

yarn add webpack-dev-server --dev

詳細配置參數可查閱官方文檔,常使用參數以下:es6

  • contentBase 指定項目根目錄
  • historyApiFallback 主要應用在單頁應用的開發場景,它依賴於HTML5 history API,若是設置爲true,全部的跳轉將指向index.html
  • inline 主要是解決了自動瀏覽器自動刷新問題
  • lazy 默認爲false,若開啓後,則webpack-dev-server再也不監測文件變化自動編譯,只有等到咱們手動刷新瀏覽器的時候纔會編譯文件
  • proxy 能夠有效的解決開發階段的跨域問題,畢竟不是全部的前端項目,都有獨立的nodejs做爲中間件去請求服務,更多的仍是把前端項目編譯後與後端服務部署在一塊兒。而開發階段又相對獨立,這時候就能夠利用proxy將前端請求轉發到後端測試服務器,不影響開發
module.exports = {
  entry: path.resolve(base, 'src', 'index.js'),
  output: {
    filename: 'bundle.js',
    path: path.resolve(base, 'dist')
  },
  devtool: 'eval-source-map',
  devServer: {
    contentBase: path.resolve(base, 'dist'),
    historyApiFallback: true,
    inline: true,
    proxy: {
      "/api": "http://localhost:8000"
    }
  }
}
小插曲,原本測試proxy的時候,本身利用php -S localhost:8000快速起了一個監聽進程,可是訪問前端的時候卻提示轉發去請求被拒絕,後來發現webpack-dev-server去轉發請求的時候是把localhost轉化爲了地址,即127.0.0.1:8000,因此在php啓動監聽進程時候,須要使用 php -S 127.0.0.1:8000或者 php -S 0.0.0.0:8000監聽全部網卡地址

webpack-dev-server的使用同webpack基本運行同樣,只是webpack-dev-server是一個不會退出的進程,並自動監控文件變化等(Ctrl+C退出)

npx webpack-dev-server --config config/webpack.config.js

更方便的打包命令

使用npx webpack --config config/webpack.config.js進行打包實際上已經很方便了,可是當咱們須要又有開發環境的配置,又有生產環境的配置,甚至還要爲命令增長其餘的環境變量的時候,這個命令簡直是又臭又長,咱們總不能每次都輸入這個繁瑣的命令,其實咱們能夠利用npm scripts,這裏是一篇阮一峯大神對npm腳本的介紹

//package.json
{  
  "scripts": {
    "build": "webpack --config config/webpack.config.js",
    "dev": "webpack-dev-server --config config/webpack.config.js"
  }
}
npm run build
npm run dev
須要注意的是在配置build&dev腳本的時候,咱們並無用 npx命令,實際上,在npm腳本中的命令,npm默認都是優先查找本項目下node_modules下是否存在對應的模塊及命令,若是沒找到,纔會查找全局的命令

多文件入口

一般一個項目中,並非只有一個js文件,而entry默認支持多文件入口,修改output跟隨入口文件名字命名便可,簡單作以下修改:

module.exports = {
  entry: {
    index: path.resolve(base, 'src', 'index.js'),
    main: path.resolve(base, 'src', 'main.js')
  },
  output: {
    filename: 'js/[name].js',
    path: path.resolve(base, 'dist')
  }
}

同時建立一個簡單的main.js進行測試,再次編譯會發如今dist/js目錄下存在編譯後的index.js和main.js文件

一切皆爲Module

webpack使人興奮的一個特性就是模塊化,易於擴展其功能。webpack支持大量的模塊導入方法,好比ES6(import)、CommonJS(require)、AMD等規範,而且加入了一些自由方法,具體的能夠看官方說明文檔。

用通俗的語言描述就是,webpack經過某個入口,去匹配各類模塊導入規範,發現一個模塊就根據對應配置尋找對應的loader去處理,如此往復,直處處理完畢全部依賴。

使用起來就是在modules字段中的rules(老版本名字爲loaders,爲保證兼容性,仍是支持這個字段的)中配置test,去匹配文件(js、css、圖片資源等等),而後把這個文件交給合適的loader處理便可,因此但凡新出的框架,若是用到了獨特的語法功能,都會配套提供對應的loader工具

使用ES6等新特性

目前雖然瀏覽器對ES6新特性的支持度都很是高,但還是有部分場景下,咱們只能運行ES5的代碼,這時候就須要利用到js轉碼屆的特斯拉Bebel及其插件了

yarn add babel-loader babel-core babel-preset-env
module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['env']
          }
        }
      }
    ]
  },

顯然test中匹配了全部的js文件,exclude字段去除了項目中依賴庫裏的文件,use則是配置對應的loader。其中對於options,是做爲參數傳遞給babel-loader的,babel的相關參數能夠參考babel的官方網站,其中presets做爲最主要的參數,告訴babel按照那種規則去解析代碼,固然env是一個組合,包含es201五、es2016等,當presets參數包含多個值時,babel的處理規則是倒序的,"es2017","es2016",babel會先去匹配es2016的規則。另外,對於babel的配置,也能夠經過在根目錄創建.babelrc方式去配置:

//.babelrc
{
  "presets": [
    "env"
  ]
}

固然除了擴展js的語法,有時候咱們還須要擴展js的功能,好比在某些低版本的瀏覽器上運行'Hello World'.includes('Hello'),這可使用babel-polyfill這個組件,點擊這裏能夠了解其使用方法。

CSS

固然,咱們能夠只讓webpack處理js文件,繼續在html文件中經過link標籤引入css文件。但顯然webpack但願前端攻城獅們也能像模塊化編寫js同樣,進行css的編寫。咱們在src下建立css目錄,並建立main.css文件

/* src/css/main.css */
body{
  background-color: deepskyblue; /* 我喜歡填空的藍色 */
}
//src/index.js
import style from './css/main.css'

這時候使用npm run build會發現編譯報錯,這是由於webpack沒法處理載入main.css後的代碼

ERROR in ./src/css/main.css
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type.
| body{
|   background-color: red;
| }
 @ ./src/index.js 3:12-37

webpack官方提供css-loader專門處理導入的css模塊,固然css-loader也僅僅是完成了模塊的導入處理,使webpack在編譯時候再也不報錯,實際上,還須要style-loader處理導入後的css數據,自動添加到html的style標籤中。若是你在測試過程當中,僅僅添加css-loaderloader,你會發現body實際上並無變成背景藍

yarn add style-loader css-loader
//config webpack.config.js module->rules
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader"
          }, {
            loader: "css-loader",
            options: {
              modules: true,
              localIdentName: '[path][name]__[local]--[hash:base64:5]'
              }
          }
        ]
      }

固然css-loader還有一個重要配置選項,modules參數,它支持導入的css中的類名自動重命名,這樣即便在不一樣組件使用了相同的css類命名,經處理後互相之間也不會出現影響。看下效果:
clipboard.png

CSS預編譯工具

比較常見的預編譯工具也就是Sass、Less、Stylus。在使用這三個預編譯工具前,須要安裝其對應的專屬處理程序,好比Sass的處理工具node-sass。爲了配合與webpack使用,還要安裝對應的loader,好比sass-loader

yarn add sass-loader node-sass --dev

在webpack.config.js中添加對scss(Sass 3引入的新的語法格式,推薦新項目都用此)文件的支持,再起強調,webpack中loader執行順序是從右往左,從下往上。

// webpack.config.js
module.exports = {
    ...
    module: {
        rules: [{
            test: /\.scss$/,
            use: [{
                loader: "style-loader"
            }, {
                loader: "css-loader",
                options: {
                  modules: true,
                  localIdentName: '[path][name]__[local]--[hash:base64:5]'
              }
            }, {
                loader: "sass-loader"
                }
            }]
        }]
    }
};

測試一下:

/* src/sass/main.scss  */
body {
  background-color: blue;
}
.main {
  background-color: grey
}
//src/index.js
// import style from './css/main.css'
import style from './sass/main.scss'
let es6 = () => {
  console.log('run in es6')
  console.log(style)
}
es6();

其餘兩種預編譯器能夠參考各自官方文檔:less-loaderstylus-loader

像處理JS同樣處理CSS -- PostCSS

PostCSS官網介紹是,利用js轉譯css的一個工具。對PostCSS的介紹,我比較承認這篇文章,PostCSS提供了一個解析器,把css轉換爲抽象語法樹(AST),固然這個AST是可以被js處理的,而後交給各類插件處理後,再將AST轉爲css代碼,因此關鍵是這些插件能完成哪些功能。

Autoprefixer

Autoprefixer 是一個流行的 PostCSS 插件,其做用是爲 CSS 中的屬性添加瀏覽器特定的前綴。

cssnext

cssnext 插件容許開發人員在當前的項目中使用 CSS 未來版本中可能會加入的新特性。須要注意這個插件自己包含了Autoprefixer功能,因此若是使用了這個插件,則不須要Autoprefixer插件。
固然PostCSS中其實也包含支持Sass、Less等的插件,這個看我的喜愛,不過我本人卻是蠻期待嘗試下cssnext,畢竟大部分特性可能就是將來css支持的特性,提早熟悉下也算是預習。
更多插件能夠讀這篇文章,再也不敘述

安裝postcss-loader及其插件autoprefixer

yarn add postcss-loader autoprefixer --dev

webpack.config.js中添加postcss支持,注意postcss處理css文件的位置

// webpack.config.js
module.exports = {
    ...
    module: {
        rules: [{
            test: /\.scss$/,
            use: [{
              loader: "style-loader"
            }, {
              loader: "css-loader",
              options: {
                modules: true,
                localIdentName: '[path][name]__[local]--[hash:base64:5]',
                minimize: false
              }
            }, {
              loader: 'postcss-loader',
              options: {
                config: {
                  path: path.resolve(base, 'config', 'postcss.config.js')
                }
              }
            }, {
              loader: "sass-loader"
            }]
        }]
    }
};

如上面配置,postcss須要單獨的配置文件,建立config/postcss.config.js,添加以下配置:

//config/postcss.config.js
module.exports = {
  plugins: {
    'autoprefixer': {}
  }
}

圖片資源

file-loader提供了對圖片資源的loader功能,而且利用 publicPath 選項還能很好的支持cdn,配合url-loader還能對小圖片直接進行數字序列化(DataURL),減小網絡請求,提升加載速度。

yarn add url-loader file-loader --dev

增長相關配置;url-loader的默認 fallback loader就是 file-loader 爲了更直觀就寫了出來,傳遞給 file-loader 的參數也只須要寫在options中便可。這樣
background-image: url('../image/logo.jpg') 當咱們在css文件中使用這種方式引入圖片時,就會觸發url-loader去處理

//config/webpack.config.js
module.exports = {
    ...
    module: {
      rules: [    
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
              fallback: 'file-loader',
              name: '[hash:5].[ext]',
              // publicPath: 'https://cdn.j2do.com/',
              outputPath: 'images/'
            }
          }
        ]
      }
    ]
  }
}

雖不是萬能,但卻異常強大的插件(Plugins)系統

Loader是專一於處理Webpack導入的資源模塊,而插件是對Webpack功能的擴展。除了Webpack內置的插件,開發社區提供了大量優秀的插件。固然插件也是解決問題的,咱們仍是以問題爲導向,去介紹幾款插件。

利用 extract-text-webpack-plugin 分離css到獨立文件

yarn add extract-text-webpack-plugin --dev

其中ExtractTextPlugin中fallback是指定了若是不須要提取到獨立css文件中的樣式文件,則交給 style-loade 處理,其餘loader配置,跟以前沒有差別。一樣若是是預編譯文件(Sass、Less等)的話,也只須要增長對應的loader便可

module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: [
            {
              loader: "css-loader",
              options: {
                modules: true,
                localIdentName: '[path][name]__[local]--[hash:base64:5]'
              }
            }, {
              loader: 'postcss-loader',
              options: {
                config: {
                  path: path.resolve(base, 'config', 'postcss.config.js')
                }
              }
            }
          ]
        })
      }
    ]
  }
  ...
  plugins: [
    new ExtractTextPlugin("css/[name].css")
  ]
};

再次運行 yarn run build 會發現生成了獨立的css文件

利用 html-webpack-plugin 自動生成 index.html 等入口文件

隨着多入口以及css的分離,手動去寫html的入口文件也是一件麻煩事,這時候 html-webpack-plugin 就排上用途了, html-webpack-plugin 可以根據某個模板文件自動生成入口的html,包括多個入口,安裝及配置以下:

yarn add html-webpack-plugin --dev
module.exports = {
  ...
  plugins: [
    new ExtractTextPlugin("[name].css"),
    new HtmlWebpackPlugin({
      title: 'Index Page',
      template: path.resolve(base, 'src', 'template/index.html.tmpl'),
      filename: "index.html",
      chunks: ['index']
    }),
    new HtmlWebpackPlugin({
      title: 'Main Page',
      template: path.resolve(base, 'src', 'template/index.html.tmpl'),
      filename: "main.html",
      chunks: ['main']
    }),
  ]
};

建立模板文件,注意之因此命名爲 .tmpl 是爲了防止 .html 可能會被loader解析,<%= ... %> 將不會被插件識別,完成變量替換。以下圖,咱們的 title 是以變量形式在 webpack.config.js 中配置。該插件還支持多種模板文件,具體可見官方文檔

<!-- src/template/index.html.tmpl -->
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>
    <%= htmlWebpackPlugin.options.title %>
  </title>
</head>

<body>
</body>

</html>

代碼優化壓縮

前面已經將js、css等分離到單獨文件,接下來就是優化壓縮這些代碼。

對於css而言,只須要配置 css-loaderminimize 參數爲 true 便可;固然還能夠利用postcss的 cssnano 插件進行代碼的壓縮和優化,聽說 cssnano 是目前css壓縮優化中效果最好的工具。

對於js的壓縮,咱們藉助於 uglifyjs-webpack-plugin 插件

yarn add uglifyjs-webpack-plugin --dev
module.exports = {
  ...
  plugins: [
    new ExtractTextPlugin("css/[name].css"),
    new HtmlWebpackPlugin({
      title: 'Index Page',
      template: path.resolve(base, 'src', 'template/index.html.tmpl'),
      filename: "index.html",
      chunks: ['index']
    }),
    new UglifyJsPlugin()
  ]
};

藉助Eslint自動代碼規範檢測及格式化

Eslint再也不介紹,在webpack下使用Eslint須要以下依賴包:

yarn add eslint eslint-loader babel-eslint eslint-config-standard --dev

其中eslint是必須的,eslint-loader 是鏈接eslint與webpack的loader,babel-eslint 是一個eslint解析器,使其能支持es6等語法檢測,eslint-config-standard是Airbnb的規範配置,目前最流行的js規範。

安裝過程當中若是有提示 eslint-config-standard@11.0.0-beta.0" has unmet peer dependency "eslint-plugin-import@>=2.2.0"等等,直接安裝對應的包便可,好比: yarn add eslint-plugin-import --dev便可
eslint是規範js語法,因此他須要處理的是js文件,並且應該是先於全部loader去處理js文件,若是出錯或者不規範則糾正之,這裏能夠利用webpack的 enforce 屬性,設置eslint檢查,先於其餘loader
module.exports = {
  ...
  module: {
    rules: [
      {
        enforce: "pre",
        test: /\.js$/,
        exclude: /node_modules/,//注意不要檢測node_modules裏面的代碼
        loader: "eslint-loader",
        options: {
          fix: true //自動修復不規範的代碼,並非全部代碼都能自動修復的,一些縮進,引號等能直接處理
        }
      }
      ...
    ]
  }
}

同時還要爲eslint增長對應的配置文件 .eslintrc

{
  "parser": "babel-eslint",
  "extends": "standard",
  "rules": {}
}

這時候再嘗試build吧,會發現一些不規範的代碼被自動修復,固然有些不規範的代碼,沒法自動修復的,會直接致使錯誤;好比,聲明瞭一個函數,卻沒有使用!

未完待續~~(近幾天更新)

相關文章
相關標籤/搜索