Webpack 之常見見招拆招

前端的發(fā)展,大致的發(fā)展路線可以看黃玄的JavaScript 模塊化七日談。從最初的全局污染式的注入到ES6模塊化,打包工具的不斷迭代替換。主要的原因都是因為前端發(fā)展越來越復(fù)雜龐大所導(dǎo)致。

本篇文章主要是來談?wù)?webpack 在我們平時的開發(fā)工作中起到什么作用,以及我們該如何靈活的應(yīng)用它來成為我們的利器。大多數(shù)情況下我不會說明怎么使用,因為這樣會導(dǎo)致篇幅太多不容易閱覽,所以具體的配置還是得自己閱覽官方文檔。

背景

如今的前端百花齊放,不再像以前那樣直接操作 DOM 然后壓縮扔到服務(wù)器上去??此茮]啥問題,但是不斷的重復(fù)勞動力導(dǎo)致開發(fā)效率低下。

React、Vue、Angular2。Typescript、Flow、CoffeeScript、ES6。SASS、LESS。分別為前端框架、JS超集/JS新標準、CSS預(yù)處理器。以上的這些無法直接的在瀏覽器上跑,都需要轉(zhuǎn)換為 ES5/CSS 才可以。(注:ES6 可以在支持 ES6 語法的瀏覽器上運行,如Chrome)

構(gòu)建工具

無論什么構(gòu)建工具,它們做的內(nèi)容都是大同小異:代碼轉(zhuǎn)換、文件優(yōu)化、代碼分割、模塊合并、自動刷新、代碼校驗、自動分布。歷史上的構(gòu)建工具都是基于Node.js開發(fā)的。有Grunt、Gulp、Fis3Rollup、Browserify 等等。更具體可以參考前端構(gòu)建:3類13種熱門工具的選型參考

至于它們之間優(yōu)劣性以及為什么選擇webpack在網(wǎng)上有很多相關(guān)的資料可以參考,在這里就不再贅述了。

開始

基礎(chǔ)配置

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

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

Loader

Loader 主要是用于將模塊代碼轉(zhuǎn)換為可在瀏覽器運行的代碼??梢岳斫鉃榉g機。如將 Less 轉(zhuǎn)換為 CSS,Typescript 轉(zhuǎn)換為 Javascript 等。

Plugin

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

DevServer

webpack-dev-server,可以幫我們解決上面沒提到但是在開發(fā)中遇到的痛點。

  • 提供 HTTP 服務(wù)而不是使用本地文件預(yù)覽;
  • 監(jiān)聽文件的變化并自動刷新網(wǎng)頁,做到實時預(yù)覽:
  • 支持 Source Map,以方便調(diào)試。

見招拆招

交待完 webpack 的基礎(chǔ)也是重要的功能之后,我們從工作中開始,見招拆招,也就是說我們平時需要做什么,webpack 能幫我們做什么。

見招 - ES6

ES6的出現(xiàn)引入了新的語法,提高了開發(fā)效率。但是目前仍有很多瀏覽器對其標準支持不全。所以我們需要將其轉(zhuǎn)換為 ES5 以及對新 API 打 polyfill。才能正常的使用。

拆招 - Babel

Babel 是 JS 編譯器,主要功能就是將 ES6 轉(zhuǎn)為 ES5,詳看 What is Babel? · Babel。在項目根目錄創(chuàng)建.babelrc

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

在了解 Babel 后,下一步就是配置 Webpack。

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

見招 - Typescript

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

拆招 - Typescript

Typescript 官方提供了能將 Typescript 轉(zhuǎn)換成 JavaScript 的編譯器。執(zhí)行安裝npm i -g typescript,然后在根目錄新建配置編譯選項tsconfig.json。

{
  "compilerOptions": {
    "module": "commonjs", // 編譯出的代碼采用的模塊規(guī)范
    "target": "es5", // 編譯出的代碼采用 ES 的哪個版本
    "sourceMap": true // 輸出 Source Map 以方便調(diào)試
  },
  "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 的預(yù)處理器,它們都是可以方便的管理代碼,抽離樣式公共部分,通過邏輯來書寫更加靈活的樣式代碼,從而提高效率。關(guān)于他們更多的信息可以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 文件轉(zhuǎn)換為 css 代碼,再將其交給 css-loader 處理;
2、css-loader 會找出 css 代碼中導(dǎo)入語句如@importurl(),同時支持 css modules、壓縮 css 等功能,然后交給 style-loader 處理;
3、style-loader 會講 css 轉(zhuǎn)換為字符串注入 js 代碼中。


見招 - React

React 中主要是因為其代碼中使用了 JSX 和 Class 特性,因此我們需要將其轉(zhuǎn)換為瀏覽器能識別的 JavaScript 代碼。

拆招 - Babel

我們需要依賴 babel-preset-react來完成語法上的轉(zhuǎn)換。所以我們還需要配置.babelrc,加入 React Preset。

"presets": [
  "react"
]

其實這樣就可以了。但是我們有時候會使用 React + Typescript 組合來提高我們開發(fā)效率。在上面我們提到 Typescript 的開發(fā),我們這次來修改其配置文件tsconfig.json。

{
  "compilerOptions": {
    "jsx": "react" // 開啟 JSX,支持 React
  }
}

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


見招 - Vue

Vue 沒有 React 那樣會內(nèi)置專屬語法,但它和 React 一樣,都推崇組件化和由數(shù)據(jù)驅(qū)動的思想。話不多說,直接拆招。

拆招 - vue-loader

解析 vue 主要需要 vue-loadervue-template-compilervue-loader 主要事用來解析和轉(zhuǎn)換.vue文件,提取出其中的邏輯代碼、樣式代碼以及 html 模板 template,再分別將它們交給對應(yīng)的 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 優(yōu)化生效
    "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 來進行我們的開發(fā)工作了。但是實際項目中有很多的痛點,例如代碼檢查,熱更新,CDN發(fā)布等。我們不可能每次都手動的來配置,這樣太繁瑣太浪費時間了。接下來我們通過 webpack 來優(yōu)化我們的開發(fā)體驗。

見招 - 監(jiān)聽更新

當(dāng)我們在開發(fā)階段,肯定會在期間不斷地修改源碼。但是我們不可能每一次修改就手動編譯然后刷新頁面,這明顯浪費我們的時間跟精力。于是就有了自動化監(jiān)聽更新,原理就是監(jiān)聽本地源碼包括樣式,一旦發(fā)生變化時,就會自動構(gòu)建然后刷新瀏覽器。

拆招 - webpack

通過 webpack 開啟監(jiān)聽模式,一般有兩種方式:

  • 配置webpack.config.js設(shè)置watch: true;
  • 執(zhí)行 webpack 時,可以帶上參數(shù),如 webpack --watch

它的工作原理就是通過 aggregateTimeout 設(shè)置等待時間,到該時間時就會去檢查編輯后的文件的最后編輯時間從而達到監(jiān)聽的目的。

見招 - 自動刷新瀏覽器

在上面我們提到了監(jiān)聽更新,但是更新完后瀏覽器應(yīng)該有所表現(xiàn),不然手動刷新瀏覽器的行為也是蠻愚蠢的。所以當(dāng)我們監(jiān)聽到的文件一旦發(fā)生了修改,瀏覽器就要主動去刷新瀏覽器。

拆招 - webpack-dev-server

我們使用 webpack-dev-server 模塊啟動 webpack 模塊時,webpack 模塊的監(jiān)聽模式默認會被開啟。webpack 模塊會在文件發(fā)生變化時通知 webpack-dev-server 模塊。

通過 webpack-dev-server 啟動時,有以下兩種方式可以實現(xiàn)自動刷新:

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

見招 - 模塊熱替換

在上面提到的更新后刷新是會刷新整個頁面,這樣的體驗不好。所以 webpack-dev-server 還支持模塊熱替換,就是在不刷新整個頁面的情況下只替換修改的文件,這樣不但快捷,而且數(shù)據(jù)也不會丟失。

拆招 - webpack-dev-server

實現(xiàn)模塊熱替換也有兩種方式:

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

見招 - 檢查代碼

當(dāng)我們的項目越來越龐大時,特別是多人協(xié)作開發(fā),會導(dǎo)致一個問題就是代碼會有多種風(fēng)格導(dǎo)致可讀性下降。因此我們需要在提交之前執(zhí)行自動化檢查,讓項目成員強制遵守統(tǒng)一的代碼風(fēng)格,同時也可以分析出潛在的問題。

拆招 - **lint 及 husky

**lint 這里指的是針對不同的語言使用不同的 lint 檢查工具。

  • eslint:用來檢查 JavaScript,配置 .eslintrc 來添加規(guī)則,再結(jié)合 eslint-loader 就可以通過 webpack 來執(zhí)行代碼檢查;
  • tslint:用來檢查 TypeScript,配置 tslint.json 來添加規(guī)則,再結(jié)合 tslint-loader 就可以通過 webpack 來執(zhí)行代碼檢查;
  • stylelint:用來檢查樣式文件,如 SCSS、Less等,配置.stylelintrc 來添加規(guī)則,再結(jié)合 stylelint-webpack-plugin 就可以通過 webpack 來執(zhí)行代碼檢查;

上面通過整合到 webpack 存在個問題,就是在開發(fā)過程中構(gòu)建速度會變慢很多。所以我們建議在提交的時候通過 Git Hook 來執(zhí)行我們的代碼檢查,如huskyhusky 會通過 Npm Script Hook 自動配置好 Git Hook,然后我們只需要在 package.json 添加 script 腳本,其中 precommitprepush 只需要其中一個就好了,配置如下:

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

其他

除了上面這些,我們可能還需要需要以下的配置:
加載圖片
- file-loader:將 JavaScript 和 CSS 中導(dǎo)入圖片的路徑替換成正確的路徑,并同時將其輸出到對應(yīng)位置;
- url-loader:將文件的內(nèi)容經(jīng)過 base 64 編碼后注入JavaScript 或 CSS 中。

加載SVG
- raw-loader:可以將文本文件內(nèi)容讀取出來,注入到 JavaScript 或 CSS 中。
- svg-inline-loader:跟 raw-loader 一樣,但是增加了對 svg 壓縮的功能。

優(yōu)化

區(qū)分環(huán)境

區(qū)分環(huán)境的好處我就不多解釋了,這里主要是用到了 webpack自帶(當(dāng)代碼出現(xiàn)process時,webpack會將其模塊打包進去)的 process 模塊。使用方法也很簡單 process.env.NODE_ENV 就行了。

壓縮代碼

上線后我們除了GZIP對其文件進行壓縮,我們還需要對文件本身進行壓縮進而減少網(wǎng)絡(luò)傳輸流量和提高網(wǎng)頁加載速度。這里的文件壓縮就是用到了UglifyJsPlugin 插件。詳情配置可以查看官方,需要注意的是,記得區(qū)分環(huán)境如 source-map 等。

壓縮 CSS

壓縮 CSS,用一款基于 PostCSS 的壓縮工具 cssnano。css-loader已經(jīng)內(nèi)置其模塊了,只需要開啟 css-loaderminimize 選項即可。

CDN加速

這里不是說要通過前端來做 CDN 加速的事,而是當(dāng)我們上傳靜態(tài)資源時,靜態(tài)資源需要通過 CDN 服務(wù)提供的 URL 地址去訪問,而我們要做的,就是在生成頁面時,將我們的靜態(tài)資源替換為CDN的地址
我們所說的靜態(tài)資源主要分為兩種,入口 HTML 文件以及 JS、CSS、圖片等靜態(tài)資源。前者的處理方法是存在服務(wù)器而非CDN,并且服務(wù)器不對其做緩存處理,這樣就可以保證每次請求的入口文件都是最新的;后者則會上傳 CDN 服務(wù)上,做緩存處理。
簡單的來說就是入口 HTML 文件是在每一次請求都是最新的,那么其請求的 靜態(tài)資源的 Hash 值也有可能會更新,那么只要發(fā)生變換,則去請求新的靜態(tài)資源就行了。

那么問題來了,怎么做才能每次打包新的 HTML 文件時,其請求的靜態(tài)資源的也會跟隨變化呢?webpack 及其插件提供了其功能,分別為:

  • output.publicPath 中設(shè)置 Javascript 的地址;
  • css-loader.publicPath 中設(shè)置被 CSS 導(dǎo)入的資源的地址;
  • Webplugin.stylePublicPath 中設(shè)置 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 的動態(tài)組件 & 異步組件 — Vue.js,就可以實現(xiàn)按需加載了,如:

resolve => require(['./Test'], resolve)

React
react-router 還可以配合 react-loadable,實現(xiàn)路由按需加載,如:

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

分析報告

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

最后

本篇的大多內(nèi)容是閱覽完《深入淺出 webpack》后的總結(jié)。之所以想總結(jié),是因為 webpack 的配置給人的感覺就是配置麻煩很瑣碎。因此就有了這個想法,對知識點的查漏補缺,同時也是一次對知識點的梳理。這篇文章目前主要梳理常用的一些配置、插件以及優(yōu)化。當(dāng)然這也只是冰山一角,更多的還需要自己去查閱官方文檔,不同版本也會有不同的差異性。之后遇到問題,我也會持續(xù)記錄下來。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • “用不著叫你兄長回來?!眲⒎蛉斯蜃`前,背對著謝琰,道。 “嗯?!敝x琰雖剛送了對家某人出門,意氣怏怏,在母親面...
    王氏箐娘閱讀 370評論 0 1
  • 阿育吠陀是幫助每一個人達到最好的身體健康和頭腦的清醒,而瑜伽是幫助每一個人靈魂的解脫。 阿育吠陀與西方醫(yī)學(xué)的不同處...
    瑾爺Jaya閱讀 410評論 0 3
  • 今天本來是想寫《流量池》的,但聽完之后感覺寫不出來,可能我不喜歡這類書籍吧。 我只想盡快的把他寫完,我很焦急,無法...
    王小柒aa閱讀 343評論 0 0
  • ——讀《不抱怨的世界》有感 抱怨只會讓恃強凌弱的混蛋意識到自己附近住著一個弱者。如果你看...
    李鑫律師聊法律閱讀 701評論 1 1
  • 最喜歡的夕陽-O- 夕陽。 美如畫。 圖片版權(quán)歸本人所有,圖片版權(quán)于簡書用于設(shè)置為app開屏圖,一切版權(quán)糾紛與簡書無關(guān)。
    桎梏二嫚閱讀 227評論 0 0

友情鏈接更多精彩內(nèi)容