在 webpack 中,每一個文件都是一個模塊,除了 webpack 認識的 JS 和 JSON 文件,CSS,圖片等都是模塊,模塊之間經過不一樣方式的語法會產生依賴關係,例如:css
webpack 經過loader來支持多種語言和預處理器語法編寫的模塊,這樣就能夠處理各類非 JS 內容。html
以前的配置是直接在 webpack 的打包輸出目錄下建立一個 html 頁面,並手動引入 webpack 輸出的 JS 文件。儘管引入了 WDS,能夠自動打開並刷新頁面,可是這只是一個 bundle 文件的狀況,若是項目模塊增多,須要多個 bundle 時,天然沒法再手動將 bundle 文件添加到 html 中,因此咱們須要一個插件可以將 bundle 文件自動添加到 html 中。react
HtmlWebpackPlugin 是在 webpack 打包過程當中自動生成 html 頁面,而且自動將資源文件,例如 webpack 打包輸出的 JS 文件插入到 html 頁面中的插件。根據配置,這個插件還能支持根據自定義的模板來生成 html 文件,或者根據ejs的語法規則來替換一些變量。webpack
yarn add html-webpack-plugin -D複製代碼
配置項 | 類型 | 默認值 | 用法 |
---|---|---|---|
title | String | Webpack App | html 的 title |
filename | String | 'index.html' | 生產的 html 頁面的文件名 |
template | String | src/index.ejs | 模板頁面,若是存在src/index.ejs就是用它 |
templateContent | String|Function|false | false | 代替模板使用以提供嵌入式模板 |
templateParameters | Boolean|Object|Function | false | 覆蓋模板中使用的參數 |
inject | `true | false <br />'head' | 'body'` |
scriptLoading | 'blocking' 'defer' |
'blocking' | 設置 JS 加載的方式 |
favicon | String | `` | 將給定的 favicon 路徑添加到 html 中 |
meta | Object | { } | 爲 html 模板頁面添加 meta,參見 —— HEAD#meta |
base | Object|String | false | false | 爲 html 模板頁面添加 base 標籤 |
minify | Boolean | Object | true | 根據 webpack 的mode配置來判斷當前環境,因爲mode默認是production生產環境,因此這個值默認也是true,也就是對 html 進行壓縮;也可使用一個對象來配置具體的壓縮項目,參見 —— minification |
hash | Boolean | false | 若是爲true,則將惟一的 webpack 編譯哈希值附加到全部包含的腳本和 CSS 文件中。這對於清除緩存頗有用 |
cache | Boolean | true | 僅在文件改變時從新生成 |
showErrors | Boolean | true | 錯誤詳細信息將寫入 HTML 頁面 |
chunks | Array of String | ? | 自定義添加的模塊 |
chunksSortMode | "none" "auto" "manual" Function |
"auto" | 容許控制在將塊包含到 HTML 中以前應如何對其進行排序 |
excludeChunks | Array of String | 打包時忽略的模塊,例如測試模塊等 | |
xhtml | Boolean | false | 若是設置成true,link標籤的插入形式將是自動閉合的形式,也就是閉合的箭頭括號前多一個斜槓 |
若是不使用自定義的模板,HtmlWebpackPlugin 配置最簡單,直接new HtmlWebpackPlugin()就能夠了。github
const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = { ... plugins: [new HtmlWebpackPlugin()], }複製代碼
可是實際項目每每基本都須要自定義模板,HtmlWebpackPlugin 能夠根據自定義的模板來生成 html 文件,並將 webpack 的輸出資源插入到 html 頁面中。web
HtmlWebpackPlugin 自己內置識別.ejs文件的 loader,若是你的模板使用.ejs來定義,那麼就無需安裝其餘 loader 了。固然了,HtmlWebpackPlugin 也提供了許多其它類型的模板能夠選擇,參見 —— template option。正則表達式
接下來新建一個public文件夾,將以前 dist 目錄的index.html移入其中,而後把<body>裏以前引入的main.js幹掉,將這個index.html做爲模板頁面配置在 HtmlWebpackPlugin 中,而後再把 dist 目錄刪掉。跨域
const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = { ... plugins: [new HtmlWebpackPlugin({template:"./public/index.html" })], }複製代碼
接下來,須要將 webpack-dev-server 配置裏的contentBase刪除掉,已經再也不須要提供靜態文件了,如今依據html-loader和html-webpack-plugin能自動生成一個 html 頁面做爲 webpack-dev-server 的調試頁面了,即便執行生產環境的yarn build命令也能依據靠它們將 html,icon 等靜態文件輸出到打包目錄dist中。
執行yarn start命令,就能夠看到自動在瀏覽器中打開了頁面,經過 devtool 也能看到 bundle 文件已經自動插入到指定的 html 中了。
能夠爲 html 頁面設置一個 favicon,HtmlWebpackPlugin 會根據favicon配置的路徑找到文件而後添加到 html 中。
plugins: [ new HtmlWebpackPlugin({template: "./public/index.html",favicon: "./public/favicon.ico", }), ],複製代碼
在 React 開發過程當中若是要使用 CSS,最多見的作法是在當前組件中經過import引入 CSS 文件,可是 CSS 最終須要被放在 HTML 頁面中才能被加載,解析。
要將 CSS 自動插入到 html 頁面中,須要使用style-loader,光引入style-loader還不行,得須要一個根據import語法解析 CSS 的 css-loader,css-loader負責解析 CSS 文件中的樣式生成字符串,而後style-loader默認建立<style>標籤塞入 CSS 字符串,最後插入到頁面中。
yarn add style-loader css-loader -D複製代碼
//簡單配置module.exports = { ... module:{rules:[ ... {test: /\.css$/i, use: [ {loader: "style-loader", }, {loader: "css-loader", }, ], }, ] } }複製代碼
默認是以<style>的形式將組件中引入的 CSS 插入到 DOM 中
配置項 | default | 含義 |
---|---|---|
injectType | styleTag | 把樣式插入到 html 的方式,默認是經過<style>標籤,能夠選擇linkTag,也就是<link>標籤 |
attributes | {} | 寫入<style>或者<link>標籤的屬性 |
insert | head | 插入<style>或者<link>標籤的位置,默認就是在 html 的<head>中 |
base | base 容許你經過指定一個比 DllPlugin1 使用的 css 模塊 id 大的值,來避免應用程序中的 css (或者 DllPlugin2 的 css) 被 DllPlugin1 中的 css 覆蓋問題 | |
esModule | false | 默認狀況下,style-loader 生成使用 Common JS 模塊語法的 JS 模塊;若是指定使用 ES module,有利於 tree shaking |
配置項 | 默認值 | 含義 |
---|---|---|
url | true | 默認啓用對url/image-set動態加載資源的處理 |
import | true | 默認支持@import規則 |
modules | false | 默認是基於文件名選擇是否支持 CSS Modules |
esModule | true | css-loader 生成默認使用 ES 模塊語法 |
importLoaders | 0 | 設置在 CSS 加載程序以前應用的 loader 的數量 |
sourceMap | false | 取決於 webpack 的devtool設置項,只在devtool的值不是eval和false的時候這個配置項纔有用 |
須要特別關注的是modules這個配置,它和CSS Modules相關。由於單頁面應用開發中使用 CSS 一個最大的問題就是很差維護,特別容易出現命名混亂,樣式覆蓋等問題,使用 CSS Modules 能夠有效避免這些問題。
css-loader默認是支持 CSS Modules 的,它是根據在 React 中import引入的 CSS 文件名來判斷這個 CSS 文件是否是一個模塊,若是是就去使用內置的解析規則去替換一些 class 名稱等。正則匹配文件名規則是/\.module\.\w+$/i,也就是忽略大小寫,文件名中包含.module就被看成一個 CSS 模塊。
Note:若是你eject過 CRA 的代碼就會發現,CRA 就是採用css-loader這種默認配置的方式。
所以若是你將一個 CSS 文件名命名爲xxx.module.css這種形式之後,在 React 組件中使用import引入這個 CSS 文件的時候,必須使用 CSS Modules 指定的規則,也就是把導入的 CSS 看做一個對象,在className中去使用,以下:
/* styles.module.css */.test { color: red; }複製代碼
import React, { Component } from 'react';import styles from './styles.module.css';export default class extends Component { render() {return ( <div><p className={styles.test}>12121212</p> </div>); } }複製代碼
除了使用默認配置, modules可供選擇的設置還有這些:
屬性值 | 含義 |
---|---|
true | 所有使用 CSS Modules,設置成這個之後,在 React 中引入 CSS 就必須按照上面那種 CSS Modules 的寫法 |
false | 禁用 CSS Module |
"local" | 和true是同樣的,CSS Modules 中的class只具備模塊做用域 |
"global" | 將 CSS Modules 設置成全局做用域 |
Object Type | modules能夠設置成一個對象,屬性以下表所示,見 —— Object |
以modulesObject 形式配置localIdentName 來看,localIdentName 能夠指定替換 React 中className的名稱,localIdentName 採用loader-utils#interpolatename中的模板字符串替換形式。推薦是開發環境使用"[path][name]__[local]",生產環境使用"[hash:base64]",配置一下試試。
modules: { localIdentName: isDevelopment ? "[path][name]__[local]" : "[hash:base64]", },複製代碼
開發環境的生成結果:
yarn build打包的結果:
PostCSS是一個開源的用 JS 編寫的 CSS 解析器,它可以將 CSS 文件解析成抽象語法樹,從而使用 PostCSS 的插件能夠基於抽象語法樹來轉換 CSS 的語法,例如添加瀏覽器兼容前綴-webkit-(webkit 引擎),-moz-(firefox 的排版引擎)和-ms-(IE 的排版引擎),或者將代碼開發階段使用的未被瀏覽器普遍支持的 CSS 語法轉換成兼容性更強的語法等。例如 CSS 的樣式規範工具stylelint就是基於 PostCSS 的。
postcss-loader是讓 PostCSS 能夠在 webpack 構建工做流中去解析 CSS 生成 AST 的 loader,通常來講是這樣的,在 webpack 基於模塊間的依賴關係編譯完了 JS,CSS,或者 Less 等之後,這些 CSS 文件會被送到 PostCSS 去解析成 AST,而後基於 PostCSS 的插件就能夠針對 AST 去幹一些有意義的事情,好比說兼容性修改等。
yarn add postcss-loader postcss -D複製代碼
postcss-loader的配置項是相對簡單的,只有三個:
module.exports = { module: {rules: [ {test: /\.css$/i, use: [ 'style-loader', 'css-loader', {loader: 'postcss-loader',options: { postcssOptions: {//options }, }, }, ], }, ], }, };複製代碼
單純使用postcss-loader沒什麼卵用,最主要的是引入基於PostCSS 的插件,經常使用的例如:
yarn add autoprefixer postcss-preset-env postcss-flexbugs-fixes -D複製代碼
修改上文配置,引入插件
module.exports = { module: {rules: [ {test: /\.css$/i, use: [ 'style-loader', 'css-loader', {loader: 'postcss-loader',options: { postcssOptions: {plugins: [ 'postcss-flexbugs-fixes', 'autoprefixer', 'postcss-preset-env', ], }, }, }, ], }, ], }, };複製代碼
如今用::placeholder這個僞元素來測試一下,不管是開發環境仍是生產環境都自動添加了廠商前綴,可是autoprefixer在兼容grid佈局方面有一些不足,見 —— Does Autoprefixer polyfill Grid Layout for IE,它只能轉換到 IE 10 和 IE 11 的程度。
生產環境中須要將 React 中import引入的 CSS,或者 less 等導出爲單個 CSS 文件,經過<link>標籤插入到 DOM 中,推薦使用mini-css-extract-plugin這個 webpack plugin,它能夠爲每一個包含 CSS 的 JS 文件建立一個 CSS 文件,它支持 CSS 和 SourceMap 的按需加載。相比extract-text-webpack-plugin來講,mini-css-extract-plugin特色以下:
yarn add mini-css-extract-plugin -D複製代碼
修改上文的style-loader配置,同時配置mini-css-extract-plugin內置的 loader 和 plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");module.exports = function (env) { const isDevelopment = env.NODE_ENV === "development"; const isProduction = env.NODE_ENV === "production"; return { ...module: { rules: [ ... { test: /\.css$/i, use: [ isDevelopment && { //開發環境使用style-loader loader: "style-loader", }, isProduction && { //生產環境使用mini-css-extract-plugin loader: MiniCssExtractPlugin.loader, options: {publicPath: "../../",//由於提取的CSS文件最終位於 static/css文件夾中,因此往上兩層 }, }, { loader: "css-loader", options: {modules: { localIdentName: isDevelopment ? "[path][name]__[local]": "[hash:base64]", }, }, }, ].filter(Boolean), }, ], },plugins: [ ... isProduction && //還須要配置啓用plugin部分new MiniCssExtractPlugin({ filename: "static/css/[name].[contenthash:8].css", }), , ].filter(Boolean), }; };複製代碼
執行yarn build看一下輸出的文件,已經在dist目錄下根據配置的路徑生成了 css 文件
再檢查 html 頁面,發現 CSS 文件按照路徑經過<link>的形式插入
移除無用的 CSS 可使用purgecss,purgecss的文檔介紹了不少 plugin 和框架結合的用法,其中我重點關注的是結合 CSS modules 如何使用purgecss —— How to use with CSS modules,這部份內容在purgecss的介紹網站貼出來了,GitHub 的 README 頁面並未說起,在 issue 中有一個相關問題 —— Webpack 4 + React + CSS modules stripping all classes from CSS bundle。
根據 issue 中相關討論,找到了解決方案,見 —— github.com/FullHuman/p…
yarn add @fullhuman/postcss-purgecss glob -D複製代碼
content指定一個文件路徑字符串數組,在@fullhuman/postcss-purgecss裏是指定代碼中經過import引入過 CSS 的地方,指定這些文件路徑之後就會去檢查它們引入的 CSS 文件中是否包含未使用的 CSS 代碼;
glob是一個負責根據通配符匹配文件路徑的工具,這裏使用glob.sync(pattern, [options])這個方法,去查找全部src目錄以及子目錄中.jsx結尾的 React 文件。
module.exports = { module: {rules: [ {test: /\.css$/i, use: [ 'style-loader', 'css-loader', {loader: 'postcss-loader',options: { postcssOptions: {plugins: [ 'postcss-flexbugs-fixes', 'autoprefixer', 'postcss-preset-env', ['@fullhuman/postcss-purgecss', //配置@fullhuman/postcss-purgecss{ content: [ path.join(__dirname, './public/index.html'), ...glob.sync( `${path.join(__dirname, 'src')}/**/*.jsx`, {nodir: true, }, ), ], }, ], ], }, }, }, ], }, ], }, };複製代碼
通過上述配置,現定義一個 button 組件和 CSS 文件,可是隻經過 CSS Modules 使用其中一個 CSS 類,執行構建,最終生成的 CSS chunk 確實移除了未使用過的.btn1的代碼。
.btn1 { background: red; } .btn2 { background: green; }import React, { Component } from "react";import styles from "./styles.css";export class Button1 extends Component { render() {return <button className={styles.btn2}>測試1</button>; } }複製代碼
mini-css-extract-plugin只負責生成 CSS 文件,要對生產環境打包後的 CSS 文件進行壓縮,須要使用額外的插件 —— optimize-css-assets-webpack-plugin。
yarn add optimize-css-assets-webpack-plugin -D複製代碼
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); //壓縮CSS代碼module.exports = { plugins: [new OptimizeCssAssetsPlugin({ assetNameRegExp: /\.css$/g, cssProcessor: require('cssnano'), cssProcessorPluginOptions: {preset: ['default', { discardComments: { removeAll: true } }], }, canPrint: true, }), ], };複製代碼
若是在 CSS 中使用url引入一個圖片,或者在 React 中直接import一個圖片,都須要額外的 loader 來解析圖片,webpack 文檔中給出了兩種 loader 來處理圖片資源:file-loader和url-loader。
yarn add file-loader url-loader -D複製代碼
file-loader支持解析 React 中import的圖片路徑以及在 CSS 中使用url引入的圖片,默認狀況下,file-loader會對引入的圖片從新生成一個 hash 字符串做爲替換名稱。
module.exports = { ... modules:{rules:[ ... {test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/], //注意不要忘了svgloader: "file-loader", } ] } }複製代碼
若是不對file-loader配置打包後的圖片輸出目錄,在開發環境一般沒有影響,可是在執行yarn build之後,圖片都會被放在 webpack 的output指定的根目錄下面。因此必須爲file-loader指定打包後的輸出目錄。
能夠經過name和outputPath指定圖片輸出目錄
module.exports = { ... modules:{rules:[ ... {test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],loader: "file-loader",options: { name: "[name].[hash:8].[ext]", outputPath: "static/images", }, } ] } }複製代碼
這樣的配置結果,最終打包輸出圖片的目錄會和 html 在同級目錄的static/images下
├─ dist ├─ favicon.ico ├─ index.html └─ static ├─ css │ ├─ main.css └─ images └─ picture.jpg複製代碼
可是這樣的打包目錄對於在 CSS 中經過url引入的圖片來講,它也會被放到static/images目錄下,同時路徑前綴也是static/images,因此 CSS 就找不到圖片了。這種狀況我目前找到兩種解決方式:
第一種:在以前提取 CSS 的插件mini-css-extract-plugin的 loader 配置中直接指定 CSS 的公共資源目錄,這樣在打包完了之後,CSS 中引用的資源路徑會在static/images前面再加上../../的前綴,這樣路徑就對了
isProduction && { //生產環境使用mini-css-extract-plugin loader: MiniCssExtractPlugin.loader, options: {publicPath: '../../', }, },複製代碼
第二種:經過file-loader的name的函數配置方式解決,name的函數會接收三個參數:
這種方式我我的想法是對於在 CSS 中引用的圖片,設置固定格式的文件名,例如cssimage-xxx.jpg,而後name函數在接收到文件名根據正則表達式去匹配,若是知足是 CSS 中引用的圖片文件名格式,那麼返回的文件名路徑就可使用特定的形式來指定,例如../../static/images/[name].[hash:8].[ext],若是不是就使用static/images/[name].[hash:8].[ext]。
url-loader是file-loader的升級,對於在limit限制內的小圖片,url-loader將圖片轉成Base64 編碼的數據,並經過
Data URLs放在頁面中,或者 CSS 中,而對於超過了limit限制的圖片,能夠指定後續經過file-loader處理,也能夠直接爲其指定輸出路徑。
以data://形式開頭的 Data URLs 協議是衆多 URI 協議中的一種,URI 自己就是統一資源標識符的縮寫,因此 Data URLs 也是惟一標識一個資源的形式。
Data URLs 的形式以下:
data:[<mediatype>][;base64],<data>
mediatype,也就是圖片的文件類型
;base64這部分字符串時可選的,也就是說你能夠直接將某些 mediatype 的文本嵌入到 Data URLs 中,例如 HTML,SVG 等
data,多是被 Base64 編碼之後的數據,也多是純文本
經過 Data URLs 協議引入的圖片一般以下所示:
至於 Base64,是使用 ASCII 碼中的 64 個可打印字符(a~z,A~Z,0~9以及+和/,最後還有一個=後綴)來編碼數據,這種編碼的特色是將原數據的每 6 個bit用一個打印字符來表示,也就是一個字符只能表示3/4的數據量,因此通過 Base64 編碼的數據,最終會比原始數據大1/3左右。
將 Base64 編碼應用在圖片上的話,好處有如下這些:
不用在開發時候管那些麻煩的圖片路徑配置問題;
對於小圖片,直接嵌入 HTML 頁面或者 CSS 中,節省 HTTP 請求,至關於縮短頁面資源在請求過程當中的排隊時間;
圖片直接使用 Base64 編碼之後,在 JS 中獲取圖片能夠避免一些跨域使用圖片的問題
固然,也有一些侷限性:
若是把大圖片都轉成 Base64 塞到 HTML 或者 CSS 裏,會致使頁面渲染速度明顯減慢,並且還會卡;
瀏覽器沒法針對 Base64 編碼的圖片單獨緩存,要麼緩存整個 CSS 或者 HTML 文件;
因爲 Base64 上面說的比原圖片體積大的問題,須要針對服務器開啓gzip壓縮傳輸,這樣和原圖開啓gzip傳輸基本差很少了
因此通常是推薦一些頁面的小圖標,小 logo 這些不容易改變,且圖片體積小的狀況下使用 Base64 編碼。
url-loader默認是無限制的把全部圖片都經 Base64 編碼轉換,因此必須配置大小限制limit,limit的單位是字節,推薦是10KB如下的圖片進行 Base64 編碼;對於大於10KB的圖片,使用指定文件名的形式來配置。
module.exports = { ... modules:{rules:[ ... {test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],loader: "url-loader",options: { limit: 10 * 1024, name: "static/images/[name].[hash:8].[ext]", }, } ] } }複製代碼
SVG 通常在網頁中能夠經過三種方式使用:
開發的時候,通常不會選擇第一種方式,很差看也不利於維護。因此通常是作成模塊,經過<img>或者url形式去引入。因此在上面的配置中,還須要添加一種 SVG 的文件格式匹配/\.svg$/i。
單個的 SVG 文件通常都比較小,若是按照上面的url-loader配置,SVG 通常會被轉成 Base64 而後經過 Data URLs 放在<img>或者url裏面,可是 SVG 自己是能夠直接放在 Data URLs 裏的,若是轉成 Base64 之後再放進去,數據量變大了1/3,徒增 HTML 或者 CSS 文件的體積,這是反向優化!因此不可取,對 SVG 須要額外的處理。
url-loader是推薦了一個mini-svg-data-uri來處理 SVG,這個工具能夠移除原 SVG 文件中註釋,空格等亂七八糟沒用的字符,同時會對 SVG 中的字符進行 URL 編碼,避免一些舊的 IE 上不兼容的問題,而後再經過url-loader被放在 Data URLs 裏。
yarn add mini-svg-data-uri -D複製代碼
const svgToMiniDataURI = require('mini-svg-data-uri');module.exports = { module: {rules: [ ...{test: /\.svg$/i, use: [ {loader: 'url-loader',options: { generator: content => svgToMiniDataURI(content.toString()), }, }, ], }, ], }, };複製代碼
import logo from 'logo.svg';export default class extends Component { render() {return <img src={logo} />; } }複製代碼
若是想直接在頁面裏使用這個 SVG 圖片,而不是放在<img>標籤裏,可使用 airbnb 出品的babel-plugin-inline-react-svg,這個插件我在測試的時候,會和上面的url-loader有必定的衝突。它提供的功能是:
yarn add babel-plugin-inline-react-svg -D複製代碼
module.exports = { module: {rules: [ {test: /\.m?jsx?$/, exclude: /(node_modules)/, use: { loader: "babel-loader", options: {presets: ["@babel/preset-env", "@babel/preset-react"],plugins: [ "@babel/plugin-proposal-class-properties", "inline-react-svg", //注意到這是一個babel的plugin引入 ].filter(Boolean), }, }, ... } }複製代碼
測試一下,SVG 能夠經過組件的方式被插入到頁面中了。
import Logo from 'logo.svg';export default class extends Component { render() {return <Logo />; } }複製代碼
而當我在項目配置resolve.alias的時候,想用 alias 去引入一個 SVG 組件時,babel-plugin-inline-react-svg不支持 alias 解析,一直報錯。
import View from '@/assets/icons/view.svg';複製代碼
我在babel-plugin-inline-react-svg的 issue 也提了這個問題,插件做者回復是由於 babel 在 webpack 應用 alias 以前已經開始運行了。後來找到了兩個解決方法:
一是安裝babel-plugin-module-resolver,這個是 babel 的 plugin,幫助 babel 解析 alias 路徑問題,配合babel-plugin-inline-react-svg使用。
yarn add babel-plugin-module-resolver -D複製代碼
module.exports = { module: {rules: [ {test: /\.m?jsx?$/, exclude: /(node_modules)/, use: { loader: "babel-loader", options: {presets: ["@babel/preset-env", "@babel/preset-react"],plugins: [ "@babel/plugin-proposal-class-properties", "inline-react-svg",// apply babel-plugin-inline-react-svg ["module-resolver", // apply babel-plugin-module-resolver{ alias: {"@": "./src", }, }, ], ], }, }, ... }複製代碼
另外一個解決方式是換成@svgr/webpack 這個 webpack loader 來解析 SVG 組件。
yarn add @svgr/webpack -D複製代碼
module.exports = { module: {rules: [ {test: /\.svg$/, use: [ {loader: '@svgr/webpack',options: { native: true, }, }, ], }, ], }, };複製代碼
上面的 SVG 方案都是一次引入一個 SVG 圖像,這種方式在頁面圖標多的時候很是麻煩,20 多個圖標都得 20 個import。
SVG sprite 是利用和之前 CSS sprite 相同的方案思路,將多個 SVG 圖像合併到一個 SVG 文件中,最終在頁面中只顯示一個特定 SVG 圖像。SVG sprite 只適合多個小的 SVG 圖標整合,對於大的 SVG 圖像看成普通圖片用上面的方法處理比較合適。
通過必定的 webpack 配置,在 React 中能夠直接使用 SVG sprite,方法是先將全部 SVG 圖像在項目外面合成一個 SVG spritesprite.svg,內容以下:
<svg width="0" height="0" class="hidden"> <symbol viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" id="view"><path d="M512 608a96 96 0 1 1 0-192 96 96 0 0 1 0 192m0-256c-88.224 0-160 71.776-160 160s71.776 160 160 160 160-71.776 160-160-71.776-160-160-160"></path><path d="M512 800c-212.064 0-384-256-384-288s171.936-288 384-288 384 256 384 288-171.936 288-384 288m0-640C265.248 160 64 443.008 64 512c0 68.992 201.248 352 448 352s448-283.008 448-352c0-68.992-201.248-352-448-352"></path> </symbol> <symbolviewBox="0 0 1024 1024"xmlns="http://www.w3.org/2000/svg"id="view_off" ><path d="M512 800c-66.112 0-128.32-24.896-182.656-60.096l94.976-94.976A156.256 156.256 0 0 0 512 672c88.224 0 160-71.776 160-160a156.256 156.256 0 0 0-27.072-87.68l101.536-101.536C837.28 398.624 896 493.344 896 512c0 32-171.936 288-384 288m96-288a96 96 0 0 1-96 96c-14.784 0-28.64-3.616-41.088-9.664l127.424-127.424C604.384 483.36 608 497.216 608 512m-480 0c0-32 171.936-288 384-288 66.112 0 128.32 24.896 182.656 60.096l-417.12 417.12C186.72 625.376 128 530.656 128 512m664.064-234.816l91.328-91.328-45.248-45.248-97.632 97.632C673.472 192.704 595.456 160 512 160 265.248 160 64 443.008 64 512c0 39.392 65.728 148.416 167.936 234.816l-91.328 91.328 45.248 45.248 97.632-97.632C350.528 831.296 428.544 864 512 864c246.752 0 448-283.008 448-352 0-39.392-65.728-148.416-167.936-234.816"></path><path d="M512 352c-88.224 0-160 71.776-160 160 0 15.328 2.848 29.856 6.88 43.872l58.592-58.592a95.616 95.616 0 0 1 79.808-79.808l58.592-58.592A157.76 157.76 0 0 0 512 352"></path> </symbol></svg>複製代碼
而後很關鍵的,這是一個靜態文件,個人作法是把它直接和 HTML 頁面放在一個文件夾下,也就是都放在直接建的public裏面。在開發環境下,須要在 WDS 裏面經過contentBase配置靜態文件的目錄,表示服務器在請求的時候要帶上這些靜態文件,以下:
module.exports = { ... devServer:{ ...contentBase: "public", } }複製代碼
這時候就能夠直接在 React 裏面經過<use>使用上面sprite.svg裏的任意一個圖標了,還能經過fill,height或者width等屬性修改圖標。注意兩點:
export default class extends Component { render() {return ( <div><svg> <use href="sprite.svg#view" fill="red" /></svg><svg> <use href="sprite.svg#view_off" /></svg> </div>); } }複製代碼
若是要執行打包yarn build,須要藉助一個 webpack 的插件 —— copy-webpack-plugin,這個插件能夠將指定目錄的文件複製到另外一個目錄中,而且複製過程還能夠藉助一些工具來壓縮文件,例如可使用SVGO的工具來優化 SVG 文件
yarn add copy-webpack-plugin -D複製代碼
const CopyPlugin = require('copy-webpack-plugin');module.exports = { plugins: [new CopyPlugin({ patterns: [{ from: 'public/sprite.svg' }], //將sprite.svg複製到打包輸出目錄}), ], };複製代碼
這種方式十分簡單,而且能節省 SVG 文件的請求;可是不利於維護,圖標更新須要手動修改文件,必須保證每一個 SVG 圖標的 id 是惟一的,圖標愈來愈多的狀況下,很亂,同時沒法按需加載,頁面只要包含一個圖標文件,整個sprite.svg都會被請求加載。
解決按需加載和方便維護的一個方法是使用 JetBrains 出品的svg-sprite-loader,它能夠在運行的時候根據組件中引入的 SVG 文件,動態地將這些文件合併成sprite.svg,而後插入到 HTML 中,測試一下:
首先,在項目中新建一個專門用來放圖標的文件夾src/assets/icons,普通的 SVG 圖像就不要放進去了,專門用來管理圖標的。
把剛纔那兩個合成sprite.svg的圖標分開放進去,因而有了下面的目錄結構:
src ├─ assets │ ├─ icons │ │ ├─ view.svg │ │ └─ view_off.svg │ ├─ images │ │ ├─ picture.svg複製代碼
在 webpack 中引入svg-sprite-loader並配置,須要注意的是使用 webpack 的include和exclude對不一樣用處的 SVG 進行區分:
yarn add svg-sprite-loader -D複製代碼
module.exports = { module: {rules: [ ...{oneOf: [ {test: /\.svg$/i, exclude: path.resolve(__dirname, 'src/assets/icons'), //忽略icon文件夾use: [ {loader: 'url-loader', }, ], }, {test: /\.svg$/i, include: path.resolve(__dirname, 'src/assets/icons'), //只處理icon文件夾use: 'svg-sprite-loader', }, ], }, ], }, };複製代碼
這樣在組件中importSVG 文件之後,能直接經過<use>使用圖標:
import '../../assets/icons/view.svg';export default class extends Component { render() {return ( <div><svg> <use href="#view" /></svg> </div>); } }複製代碼
和 CRA 集成 —— github.com/JetBrains/s…
image-webpack-loader是使用imagemin進行圖片壓縮的 loader,能夠在url-loader或者file-loader的基礎上進行圖片壓縮。
yarn add image-webpack-loader -D複製代碼
// 在url-loader的基礎上使用module.exports = function(env) { const isDevelopment = env.NODE_ENV === 'development'; return {module: { rules: [ { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/i], include: path.resolve(__dirname, 'src/assets/images'), use: [ { loader: 'url-loader', options: {limit: 10 * 1024, //10KBname: 'static/images/[name].[hash:8].[ext]', }, }, { loader: 'image-webpack-loader', //引入image-webpack-loader options: {disable: isDevelopment, // webpack@2.x and newer }, }, ], }, ], }, }; };複製代碼
默認配置狀況下,對圖片的壓縮能夠減小約60%的圖片體積,能夠看到壓縮前圖片7.39MB,壓縮後僅爲2.32MB
須要注意的是,自己壓縮圖片是一件緩慢的事,我在上面一張圖片的壓縮大概耗費了 6s 的時間呢,因此指定disable: isDevelopment在開發環境下會禁用image-webpack-loader。
通常狀況下,字體文件能夠用做文本的渲染,或者用來處理 IconFont,即經過@font-face定義 IconFont,而後在 CSS 使用@font-face定義的圖標。
file-loader和url-loader都能用來處理字體文件,若是經過 CSS 的@font-face中的src來引入 web 字體,例如加載服務器上的字體文件,那麼須要 loader 來處理。
以配置使用url-loader爲例:
module.exports = { ... module: {rules:[ ... {test: [/\.ttf/i, /\.woff/i, /\.woff2/i, /\.eot/i, /\.otf/i],loader: "url-loader",include: path.resolve(__dirname, "src/assets/fonts"),options: { limit: 10 * 1024, //10KB name: "static/fonts/[name].[hash:8].[ext]", }, }, ] } }複製代碼
這樣就可使用@font-face的url加載服務器上的字體文件了
@font-face { font-family: 'heiti2'; src: url('../../assets/fonts/heiti2.ttf'); }body { font-family: 'heiti2'; }複製代碼
通常中文字體包包含大量的中文字符,致使中文字體包通常都很是的大,隨便一個均可能都是幾MB,若是網頁全局使用字體包中的字體,會影響網頁的呈現速度。
在引入字體以前對字體包進行壓縮是一個解決方案,這裏使用開源工具字蛛,使用 font-spider,首先新建一個空文件夾,內部新建一個 html 文件,而且將原字體包放在裏面,以下:
<!DOCTYPE html><html lang="zh-hans"> <head><meta charset="utf-8" /><style> @font-face {font-family: 'heiti2';src: url('./heiti2.ttf'); } body {font-family: 'heiti2'; }</style> </head></html>複製代碼
而後安裝font-spider
yarn global add font-spider複製代碼
進入剛纔新建的 html 文件的目錄,開始執行壓縮命令
font-spider ./index.html複製代碼
執行完之後,目錄下面會生成一個備份目錄.font-spider用來保存原字體文件,而原來的字體文件已經被替換成了壓縮之後的字體文件,這個壓縮效果,目錄結構以下:
minify ├─ .font-spider │ └─ heiti2.ttf ├─ heiti2.ttf ├─ index.html複製代碼
這個壓縮效果仍是十分強悍的,通過測試,原字體文件2.17MB,壓縮之後只有6KB了。不過須要注意的是,這裏的壓縮只是一個測試,在 webpack 中使用的話,目前 font-spider 沒有相應的方案,這個項目已經好久沒維護了,見討論 —— 如何在 webpack 中使用本工具 #150。若是想 webpack 配套使用,須要在打包之後,額外執行一下font-spider壓縮命令,而後可能還須要檢查一下字體引用路徑是否正確。