Webpack 之常見見招拆招

前端的發展,大體的發展路線能夠看黃玄的JavaScript 模塊化七日談。從最初的全局污染式的注入到ES6模塊化,打包工具的不斷迭代替換。主要的緣由都是由於前端發展愈來愈複雜龐大所致使。css

本篇文章主要是來談談 webpack 在咱們平時的開發工做中起到什麼做用,以及咱們該如何靈活的應用它來成爲咱們的利器。大多數狀況下我不會說明怎麼使用,由於這樣會致使篇幅太多不容易閱覽,因此具體的配置仍是得本身閱覽官方文檔。html

背景

現在的前端百花齊放,再也不像之前那樣直接操做 DOM 而後壓縮扔到服務器上去。看似沒啥問題,可是不斷的重複勞動力致使開發效率低下。前端

React、Vue、Angular2。Typescript、Flow、CoffeeScript、ES6。SASS、LESS。分別爲前端框架JS超集/JS新標準CSS預處理器。以上的這些沒法直接的在瀏覽器上跑,都須要轉換爲 ES5/CSS 才能夠。(注:ES6 能夠在支持 ES6 語法的瀏覽器上運行,如Chrome)vue

構建工具

不管什麼構建工具,它們作的內容都是大同小異:代碼轉換、文件優化、代碼分割、模塊合併、自動刷新、代碼校驗、自動分佈。歷史上的構建工具都是基於Node.js開發的。有GruntGulpFis3RollupBrowserify 等等。更具體能夠參考前端構建:3類13種熱門工具的選型參考node

至於它們之間優劣性以及爲何選擇webpack在網上有不少相關的資料能夠參考,在這裏就再也不贅述了。react

開始

基礎配置

/// webpack.config.js
const path = require('path');
module.exports = {
  // js 執行入口文件
  entry: './main.js',
  output: {
    // 將全部依賴的模塊合併輸出到一個 bundle.js 文件
    filename: 'bundle.js',
    // 將輸出文件都放到 dist 目錄下
    path: path.resolve(__dirname, './dist'),
  }
};
複製代碼

執行 webpack --config webpack.config.js,則會在dist文件夾生成bundle.js文件,這就是最基本的 webpack 配置。更多配置查看官網webpackwebpack

Loader

Loader 主要是用於將模塊代碼轉換爲可在瀏覽器運行的代碼。能夠理解爲翻譯機。如將 Less 轉換爲 CSS,Typescript 轉換爲 Javascript 等。git

Plugin

Plugin 主要是擴展 webpack 的功能,加強 webpack 的靈活性。如extract-text-webpack-plugin,能夠將包中的文本提取到單獨的文件中,從bundle.js提取 css 到單獨的文件出來等。web

DevServer

webpack-dev-server,能夠幫咱們解決上面沒提到可是在開發中遇到的痛點。vue-router

  • 提供 HTTP 服務而不是使用本地文件預覽;
  • 監聽文件的變化並自動刷新網頁,作到實時預覽:
  • 支持 Source Map,以方便調試。

見招拆招

交待完 webpack 的基礎也是重要的功能以後,咱們從工做中開始,見招拆招,也就是說咱們平時須要作什麼,webpack 能幫咱們作什麼。

見招 - ES6

ES6的出現引入了新的語法,提升了開發效率。可是目前仍有不少瀏覽器對其標準支持不全。因此咱們須要將其轉換爲 ES5 以及對新 API 打 polyfill。才能正常的使用。

拆招 - Babel

Babel 是 JS 編譯器,主要功能就是將 ES6 轉爲 ES5,詳看 What is Babel? · Babel。在項目根目錄建立.babelrc

{
  // plugins 告訴 Babel 要使用哪些插件,這些插件能夠控制如何轉換代碼 。 
  "plugins": [
    [
      "transform-runtime",
      {
        "polyfill": false
      }
    ],
  ],
  // presets屬性告訴 Babel要轉換的源碼使用了哪些新的語法特性,一個 Presets對一組新語法的特性提供了支持,多個 Presets 能夠疊加。
  "presets": [
    [
      // 除此以外,還有往上的標準如 ES2016等 以及 Env,其中 Env 包含ES 標準的最新特性
      "es2015", 
      {
        "modules": false
      }
    ],
    // 社區提出卻還未入標準的新特性,有stage0 - stage4,被歸入的可能性依次增長
    "stage-2",
    // 特定應用場景語法特性
    "react"
  ]
}
複製代碼

在瞭解 Babel 後,下一步就是配置 Webpack。

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
      }
    ]
  }
}
複製代碼

見招 - Typescript

Typescript 是 Javascript 類型的超集,它能夠編譯成純 Javascript。TypeScript—JavaScript的超集

拆招 - Typescript

Typescript 官方提供了能將 Typescript 轉換成 JavaScript 的編譯器。執行安裝npm i -g typescript,而後在根目錄新建配置編譯選項tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs", // 編譯出的代碼採用的模塊規範
    "target": "es5", // 編譯出的代碼採用 ES 的哪一個版本
    "sourceMap": true // 輸出 Source Map 以方便調試
  },
  "exclude": [
    "node_modules"
  ]
}
複製代碼

配置完tsconfig.json,咱們就能夠配置 Webpack。

module.exports = {
  ...
  resolve: {
    extensions: ['.ts']
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: 'ts-loader'
      }
    ]
  },
  devtool: 'source-map',
}
複製代碼

見招 - SASS/LESS

SASS 和 LESS 都是 CSS 的預處理器,它們都是能夠方便的管理代碼,抽離樣式公共部分,經過邏輯來書寫更加靈活的樣式代碼,從而提升效率。關於他們更多的信息能夠Sass: Syntactically Awesome Style SheetsGetting started | Less.js去查看。

拆招 - SASS-LOADER / LESS-LOADER

安裝完sass-loaderless-loader以後,直接配置Webpack。

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss/, 或 /\.less/
        use: ['style-loader', 'css-loader', 'sass-loader'] 或 ['style-loader', 'css-loader', 'less-loader']
      }
    ]
  }
}
複製代碼

其處理流程以下:

  1. 經過 loader 將 sass/less 文件轉換爲 css 代碼,再將其交給 css-loader 處理;
  2. css-loader 會找出 css 代碼中導入語句如@importurl(),同時支持 css modules、壓縮 css 等功能,而後交給 style-loader 處理;
  3. style-loader 會講 css 轉換爲字符串注入 js 代碼中。

見招 - React

React 中主要是由於其代碼中使用了 JSX 和 Class 特性,所以咱們須要將其轉換爲瀏覽器能識別的 JavaScript 代碼。

拆招 - Babel

咱們須要依賴 babel-preset-react來完成語法上的轉換。因此咱們還須要配置.babelrc,加入 React Preset。

"presets": [
  "react"
]
複製代碼

其實這樣就能夠了。可是咱們有時候會使用 React + Typescript 組合來提升咱們開發效率。在上面咱們提到 Typescript 的開發,咱們此次來修改其配置文件tsconfig.json

{
  "compilerOptions": {
    "jsx": "react" // 開啓 JSX,支持 React
  }
}
複製代碼

至於 Webpack 的配置,其實不用太多的改動,只須要支持下/\.tsx/後綴文件就行。


見招 - Vue

Vue 沒有 React 那樣會內置專屬語法,但它和 React 同樣,都推崇組件化和由數據驅動的思想。話很少說,直接拆招。

拆招 - vue-loader

解析 vue 主要須要 vue-loadervue-template-compilervue-loader 主要事用來解析和轉換.vue文件,提取出其中的邏輯代碼、樣式代碼以及 html 模板 template,再分別將它們交給對應的 Loader 去處理,如 template 則就是由 vue-template-compiler 去處理的。

/// webpack
module: {
  rules: [
    {
      test: /\.vue$/,
      use: ['vue-loader'],
    },
  ]
}
複製代碼

一樣,假如咱們須要 Vue + Typescript 組合呢?從 Vue 2.5 開始,就提供了對TS 的支持。配置 tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015", // 用於使 Tree Shaking 優化生效
    "moduleResolution": "node",
  }
}
複製代碼

除此以外還須要在聲明文件 vue-shims.d.ts 定義 vue 類型:

declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}
複製代碼

修改 webpack 配置文件。

module: {
  rules: [
    {
      test: /\.ts$/,
      loader: 'ts-loader',
      exclude: /node_modules/,
      options: {
        appendTsSuffixTo: [/\.vue$/],
      }
    }
  ]
}
複製代碼

到這裏爲止,咱們就能夠經過 webpack 來進行咱們的開發工做了。可是實際項目中有不少的痛點,例如代碼檢查,熱更新,CDN發佈等。咱們不可能每次都手動的來配置,這樣太繁瑣太浪費時間了。接下來咱們經過 webpack 來優化咱們的開發體驗。

見招 - 監聽更新

當咱們在開發階段,確定會在期間不斷地修改源碼。可是咱們不可能每一次修改就手動編譯而後刷新頁面,這明顯浪費咱們的時間跟精力。因而就有了自動化監聽更新,原理就是監聽本地源碼包括樣式,一旦發生變化時,就會自動構建而後刷新瀏覽器。

拆招 - webpack

經過 webpack 開啓監聽模式,通常有兩種方式:

  • 配置webpack.config.js設置watch: true;
  • 執行 webpack 時,能夠帶上參數,如 webpack --watch

它的工做原理就是經過 aggregateTimeout 設置等待時間,到該時間時就會去檢查編輯後的文件的最後編輯時間從而達到監聽的目的。

見招 - 自動刷新瀏覽器

在上面咱們提到了監聽更新,可是更新完後瀏覽器應該有所表現,否則手動刷新瀏覽器的行爲也是蠻愚蠢的。因此當咱們監聽到的文件一旦發生了修改,瀏覽器就要主動去刷新瀏覽器。

拆招 - webpack-dev-server

咱們使用 webpack-dev-server 模塊啓動 webpack 模塊時,webpack 模塊的監聽模式默認會被開啓。webpack 模塊會在文件發生變化時通知 webpack-dev-server 模塊。

經過 webpack-dev-server 啓動時,有如下兩種方式能夠實現自動刷新:

  • webpack-dev-server(默認):向要開發的網頁注入代理客戶端代碼,經過代理客戶端去刷新整個頁面;
  • webpack-dev-server --inline false:將要開發的網頁裝進一個 iframe 中,經過刷新 iframe 去看到最新效果。

見招 - 模塊熱替換

在上面提到的更新後刷新是會刷新整個頁面,這樣的體驗很差。因此 webpack-dev-server 還支持模塊熱替換,就是在不刷新整個頁面的狀況下只替換修改的文件,這樣不但快捷,並且數據也不會丟失。

拆招 - webpack-dev-server

實現模塊熱替換也有兩種方式:

  • webpack-dev-server-hot
  • HotModuleReplacementPlugin(推薦)

見招 - 檢查代碼

當咱們的項目愈來愈龐大時,特別是多人協做開發,會致使一個問題就是代碼會有多種風格致使可讀性降低。所以咱們須要在提交以前執行自動化檢查,讓項目成員強制遵照統一的代碼風格,同時也能夠分析出潛在的問題。

拆招 - **lint 及 husky

**lint 這裏指的是針對不一樣的語言使用不一樣的 lint 檢查工具。

  • eslint:用來檢查 JavaScript,配置 .eslintrc 來添加規則,再結合 eslint-loader 就能夠經過 webpack 來執行代碼檢查;
  • tslint:用來檢查 TypeScript,配置 tslint.json 來添加規則,再結合 tslint-loader 就能夠經過 webpack 來執行代碼檢查;
  • stylelint:用來檢查樣式文件,如 SCSS、Less等,配置.stylelintrc 來添加規則,再結合 stylelint-webpack-plugin 就能夠經過 webpack 來執行代碼檢查;

上面經過整合到 webpack 存在個問題,就是在開發過程當中構建速度會變慢不少。因此咱們建議在提交的時候經過 Git Hook 來執行咱們的代碼檢查,如huskyhusky 會經過 Npm Script Hook 自動配置好 Git Hook,而後咱們只須要在 package.json 添加 script 腳本,其中 precommitprepush 只須要其中一個就行了,配置以下:

{
  "scripts": {
    // 在執行 git commit 前會執行的腳本 
    "precommit": "npm run lint",
    //在執行 git push 前會執行的腳本 
    "prepush": "lint",
    // 調用 eslint、stylelint 等工具檢查代碼
    "lint": "eslint && stylelint"
  }
}
複製代碼

其餘

除了上面這些,咱們可能還須要須要如下的配置: 加載圖片 - file-loader:將 JavaScript 和 CSS 中導入圖片的路徑替換成正確的路徑,並同時將其輸出到對應位置; - url-loader:將文件的內容通過 base 64 編碼後注入JavaScript 或 CSS 中。

加載SVG - raw-loader:能夠將文本文件內容讀取出來,注入到 JavaScript 或 CSS 中。 - svg-inline-loader:跟 raw-loader 同樣,可是增長了對 svg 壓縮的功能。

優化

區分環境

區分環境的好處我就很少解釋了,這裏主要是用到了 webpack自帶(當代碼出現process時,webpack會將其模塊打包進去)的 process 模塊。使用方法也很簡單 process.env.NODE_ENV 就好了。

壓縮代碼

上線後咱們除了GZIP對其文件進行壓縮,咱們還須要對文件自己進行壓縮進而減小網絡傳輸流量和提升網頁加載速度。這裏的文件壓縮就是用到了UglifyJsPlugin 插件。詳情配置能夠查看官方,須要注意的是,記得區分環境如 source-map 等。

壓縮 CSS

壓縮 CSS,用一款基於 PostCSS 的壓縮工具 cssnanocss-loader已經內置其模塊了,只須要開啓 css-loaderminimize 選項便可。

CDN加速

這裏不是說要經過前端來作 CDN 加速的事,而是當咱們上傳靜態資源時,靜態資源須要經過 CDN 服務提供的 URL 地址去訪問,而咱們要作的,就是在生成頁面時,將咱們的靜態資源替換爲CDN的地址。 咱們所說的靜態資源主要分爲兩種,入口 HTML 文件以及 JS、CSS、圖片等靜態資源。前者的處理方法是存在服務器而非CDN,而且服務器不對其作緩存處理,這樣就能夠保證每次請求的入口文件都是最新的;後者則會上傳 CDN 服務上,作緩存處理。 簡單的來講就是入口 HTML 文件是在每一次請求都是最新的,那麼其請求的 靜態資源的 Hash 值也有可能會更新,那麼只要發生變換,則去請求新的靜態資源就好了。

那麼問題來了,怎麼作才能每次打包新的 HTML 文件時,其請求的靜態資源的也會跟隨變化呢?webpack 及其插件提供了其功能,分別爲:

  • output.publicPath 中設置 Javascript 的地址;
  • css-loader.publicPath 中設置被 CSS 導入的資源的地址;
  • Webplugin.stylePublicPath 中設置 CSS 文件的地址。

提取公共代碼

webpack 有個專門用於提取多個 Chunk 中公共部分的插件 CommonsChunkPlugin,用法以下:

const ComrnonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

new CommonsChunkPlugin({
  // 從 a、b chunk 提取共同的代碼模塊
  chunks: ['a', 'b'],
  // 將其封裝到 common 新 chunk
  name: 'common',
}) 

複製代碼

按需加載

在這裏只針對 Vue、React 來講。目前比較流行的作法就是在路由上作處理。

Vue vue-router 經過 vue 的動態組件 & 異步組件 — Vue.js,就能夠實現按需加載了,如:

resolve => require(['./Test'], resolve)
複製代碼

React react-router 還能夠配合 react-loadable,實現路由按需加載,如:

function asyncLoad (loader) {
  return Loadable({ loader });
}
asyncLoad(() => import('./Test'));
複製代碼

分析報告

webpack 自帶分析功能webpack --profile --json > stats.json,也能夠安裝可視化分析工具webpack-bundle-analyzer更加直觀的觀察項目的狀況。

最後

本篇的大多內容是閱覽完《深刻淺出 webpack》後的總結。之因此想總結,是由於 webpack 的配置給人的感受就是配置麻煩很瑣碎。所以就有了這個想法,對知識點的查漏補缺,同時也是一次對知識點的梳理。這篇文章目前主要梳理經常使用的一些配置、插件以及優化。固然這也只是冰山一角,更多的還須要本身去查閱官方文檔,不一樣版本也會有不一樣的差別性。以後遇到問題,我也會持續記錄下來。

相關文章
相關標籤/搜索