前言
在學習使用webpack時,我們需要明白無論它怎麼設計,它的工作原理、流程是什麼,最根本的它處理的還是HTML文檔中的HTML標籤、JavaScript、CSS、圖片等資源,而且最終的處理結果依然必須是一個HTML文檔,包括DOM、JavaScript、CSS,而CSS在文檔中的存在方式,有三種:行內樣式,內聯樣式,外鏈樣式,行內樣式使用方式早已不推薦,所以webpack處理CSS方式也就兩種:
- 內聯樣式: 以
<style>
標籤方式在HTML文檔中嵌入樣式; - 外鏈樣式: 打包生成CSS文件,通過
<link>
標籤引入樣式;
webpack與CSS
我們知道,webpack本質是隻能處理JavaScript的,而對於其他資源,需要使用加載器和插件將其處理成JavaScript模塊,然後進行模塊依賴管理。webpack提供style-loader
和css-loader
兩個加載器支持我們模塊化CSS,因此可以在其他模塊內直接引入。
- 安裝
npm install --save-dev style-loader css-loader
- 配置
在webpack配置文件的模塊加載器選項中添加如下配置:
module: {
loaders: [
{ test: /\.css$/, loader: "style-loader!css-loader" }
]
}
當然爲了方便使用引用路徑,還可以配置路徑片段別名:
alias: {
styles: path.resolve(__dirname, 'src/styles/')
}
此時,import 'styles/index.css';
等同於使用相對路徑,如import '../src/styles/indx.css';
- 使用
配置好以後,假如我們在styles
目錄下創建了一個index.css
文件,現在可以在JavaScript文件中直接引入該CSS: import 'styles/index.css';
或 require('styles/index.css');
css內容如下:
html, body {
width: 100%;
height: 100%;
}
.container {
color: red;
}
頁面展示如圖:
內聯樣式
前面提到了webpack處理CSS的方式有兩種,第一種是以內聯方式在頁面<head>
標籤內動態插入<style>
內聯樣式,這種方式也是webpack的默認處理方式,只需要簡單配置如下加載器:
{
test: /\.css$/,
exclude: /node_modules/,
loader: 'style-loader!css-loader'
// or
// loaders: ['style-loader', 'css-loader']
}
webpack加載器解析順序
如上面代碼所示,無論是字符串語法style-loader!css-loader
,亦或是數組語法['style-loader', 'css-loader']
,webpack解析規則都是從右至左,依次解析並執行加載器處理文件,前一加載器處理的輸出就是下一加載器處理的輸入,直到最後加載器處理完成;此處即webpack先調用css-loader
加載器處理css文件,然後將處理結果傳遞給style-loader
加載器,style-loader
接受該輸入繼續處理。
css-loader
我們已經反覆強調,webpack只能處理JavaScript,所以對於其他諸如css或圖片等資源需要使用加載器將其轉換輸出爲JavaScript模塊,webpack才能繼續處理。
css-loader
加載器的作用就是支持我們像使用JavaScript模塊一樣在JavaScript文件中引用CSS文件,如require ('./index.css')
,所以你可以認爲其作用是將CSS文件轉換成JavaScript模塊,於是我們可以直接通過引入JavaScript模塊的方式直接引用。
參數
css-loader
有兩個常用參數:
- modules: {boolean}指定是否使用CSS模塊(如:local和:global設置局部或全局樣式規則),默認是false,開啓設置如
css-loader?modules
; - importLoaders: {number}指定
css-loader
加載器之前使用的加載器數量,默認是0,設置如css-loader?importLoaders=1
;
style-loader
無論webpack怎麼處理CSS文件,最終都需要將其輸出到頁面,才能實際使用該CSS規則,style-loader
加載器就是將CSS以內聯方式插入到頁面文檔,即:針對每一個輸入(通過require
引入,使用css-loader
轉換爲JavaScript模塊,傳遞給style-loader
作爲輸入),style-loader
在頁面<head>
標籤內插入一個<style>
標籤,該標籤內樣式規則即此輸入內容,如下實例:
外鏈樣式
當然,我們並不總是希望所有樣式都以內聯方式存在頁面中,很多時候我們也希望通過外鏈方式使用樣式表,特別是樣式規則較多的時候,webpack開發者們當然考慮了這樣的需求。
webpack提供的style-loader
加載器默認是以內聯方式將樣式插入文檔,我們需要使用webpack extract-text-webpack-plugin
插件以實現輸出單獨CSS文件。
Extract Text Plugin
- 安裝
首先安裝該插件:
npm install --save-dev extract-text-webpack-plugin
- 配置
然後添加如下配置:
var ExtractTextPlugin = require('extract-text-webpack-plugin');
...
module: {
loaders: [
{
test: /\.css$/,
exclude: /node_modules/,
// 老版本 loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
loader: ExtractTextPlugin.extract({
fallback:'style-loader',
use: 'css-loader'
})
}
]
},
plugins: [
// 生成獨立css文件
new ExtractTextPlugin({
filename: 'css/[name].css'
})
]
運行webpack
命令,我們會看到在dist/css/
文件夾下生成相應的CSS文件。
-
參數
-
filename {String | Function}
Extract Text Plugin爲每個入口生成一個CSS文件,所以對於多入口項目需要指定
filename
參數[name]或[id]或[contenthash]
生成唯一識別文件名; -
disable {Boolean}
禁用此插件;
-
allChunks {Boolean}
allChunks: true;
時指定從所有模塊中抽取CSS輸出至單獨CSS文件,包括異步引入的額外模塊;此插件默認是隻抽取初始模塊內引入的CSS;
-
-
extract方法
該方法可以以參數指定加載器,然後接受該加載器的輸出,進行處理。需要在加載器和插件配置中同時聲明相關配置,才能實現效果;在加載器配置中調用其extract
方法傳入通常以下兩個參數:
1. use: 將CSS轉換成模塊的加載器;
2. fallback: 對於不被抽取輸出至單獨CSS文件的CSS模塊使用的加載器,上例中`style-loader`即說明以內聯方式使用,該加載器通常在`allChunks: false`時處理額外的模塊;
filename與output
在上一篇介紹了輸出文件配置output
相關內容,其中:
output.path
是webpack處理文件後輸出的路徑,對於CSS文件輸出依然適用,即CSS文件也將輸出至該目錄;output.publicPath
是指瀏覽器訪問資源時的相對基礎路徑,規則是:output.publicPath + output.filename
;
你可以看到在本系列文章實例中filename都添加了前綴目錄,如css
和scripts
,你可能看到很多項目是不添加的,但文件入口較多時建議分類型目錄輸出,而且需要記得在瀏覽器訪問資源時也需要添加該目錄層級。
CSS預處理器
通常在開發較複雜的應用時,我們都會選擇一種CSS的強化輔助工具,以更高效,更方便的使用CSS開發應用樣式,這些拓展工具就是所說的CSS預處理器.
**CSS預處理器(preprocessors)**在CSS語法的基礎上增加了變量 (variables)、嵌套 (nested rules)、混合 (mixins)、導入 (inline imports) 等高級功能,令CSS更加強大與優雅,有助於更好地組織管理樣式文件,以及更高效地開發項目。
目前最常見的CSS預處理器有LESS,SASS,Stylus,個人用過的是前兩種,使用SASS的還是居多。
SASS
- 安裝
npm install --save-dev sass-loader
安裝sass-loader
以後會發現,package.json中多了一個node-sass
依賴,這是使用SASS必須的。
- 配置
然後添加以下配置:
{
test: /\.s[ac]ss$/,
exclude: /node_modules/,
loader: 'style-loader!css-loader!sass-loader'
}
如上,配置中傳遞了三個加載器,相對於前文處理CSS文件的加載器,在最後面多了一個sass-loader
,首先加載sass-loader
加載器處理SASS文件成CSS代碼,然後繼續按照前文描述流程處理CSS文件。
Extract Text Plugin
和處理CSS文件一樣,上述配置最終通過style-loader
將轉換後的CSS代碼內聯到頁面,我們需要使用Extract Text Plugin
生成單獨CSS文件,以外鏈方式引用:
{
test: /\.s[ac]ss$/,
exclude: /node_modules/,
loader: ExtractTextPlugin.extract({
fallback:'style-loader',
use: [
'css-loader',
'sass-loader'
]
})
}
...
// 生成獨立css文件
new ExtractTextPlugin({
filename: 'css/[name].css'
})
CSS後處理器
前面講到CSS預處理器,如SASS,他們提供對CSS的拓展,包括語法拓展,高級特性拓展,如嵌套,變量,自動處理添加屬性前綴等,使得我們可以以其定義的語法與模板方式更高效的編寫CSS,然而這些預處理器都是另外對CSS進行拓展,各自定義了語法和模板,其處理流程是對代碼進行解析處理,然後轉換成CSS代碼。
不同預處理器有各自的定義和規範,假如你需要從LESS轉到SASS,源代碼轉換成本和學習成本頗高,而接下來要介紹的CSS後處理器並沒有這個問題。
不同於預處理器預定義好一個語法和模板,然後對按照該語法和模板編寫的代碼進行處理轉換成CSS,其輸入是自定義語法文件,輸出是CSS代碼;**後處理器(postprocessor)**是對原生CSS代碼根據配置進行處理,其輸入輸出依然是CSS代碼。
postcss
現在最受歡迎的CSS後處理器,就是postcss:
PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more. PostCSS是一個使用Js插件轉換樣式的根據,插件支持拓展CSS,如變量,混合,CSS屬性語法兼容,行內圖片等等功能。
特性
不同於SASS提供一個功能性拓展工具,postcss更多的是提供一個CSS高效開發工具解決方式,其本身只包含CSS解析器只能將CSS處理成一棵抽象語法樹(AST),同時提供一個豐富的CSS節點樹API,可以對語法樹進行操作,另外它有一個高拓展性的插件系統支持我們通過引入不同插件對CSS進行處理,一個插件的輸出還可以是下一個插件的輸入,更值得一提的是,這些插件都是JavaScript插件,前端開發者們很容易就能根據項目需求定製自己的插件,所以可以總結幾點一以下特性:
- postcss只處理CSS,概念簡潔;
- 提供高拓展性的插件系統支持按需引入不同插件,實現不同處理;
- 使用JavaScript插件,開發者可以很方便定製項目插件;
- 提供CSS節點樹API,可以高效的進行CSS處理;
- 安裝
在webpack中使用,需要先安裝對應加載器:
npm install --save-dev postcss-loader
插件
postcss目前有200+插件,足夠滿足絕大部分項目開發需求,可以查看postcss插件,我們介紹幾個主要使用的插件。
Autoprefixer
回顧一下在預處理器中,如果我們需要爲CSS代碼添加屬性前綴,需要這麼實現呢?對於Sass,我們通常使用mixin,即混合宏,處理CSS屬性前綴,如:
// 定義
@mixin prefix-animation($animation-name){
animation:$animation-name;
-webkit-animation:$animation-name;
}
// 使用
body{
@include prefix-animation(loading .5s linear infinite);
}
如上,我們需要按照定義的語法和模板:先定義一個mixin,然後通過@include
方式使用,最後才能輸出添加前綴的CSS代碼,當代碼越來越多時,我們需要定義的mixin也會越來越多,而且不同預處理器定義的語法和模板都有差異,學習成本、轉換成本都很可能令人難以接受。
那麼postcss插件怎麼處理的呢?postcss提供了Autoprefixer
插件處理CSS屬性前綴:
Autoprefixer插件基於Can I Use的數據,對CSS規則進行前綴處理。
- 安裝
首先還是要安裝Autoprefixer
:
npm install --save-dev autoprefixer
- 配置
添加如下配置:
module: {
loaders: [
{
test: /\.css$/,
exclude: /node_modules/,
loaders: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [
require('autoprefixer')({
browsers: ['last 2 versions']
})
]
}
}
]
}
]
}
如上,我們知道postcss
是一個樣式開發解決方案,其特定功能需要引入插件實現,上例中在指定postcss-loader
加載器時爲其設置了插件配置autoprefixer
;當然webpack還支持直接設置一個postcss
配置文件,然後在項目根目錄創建postcss.config.js
配置文件,內容格式如下:
module.exports = {
plugins: [
require('autoprefixer')({
browsers: ['last 2 versions']
})
// or just require('autoprefixer')
]
}
使用autoprefixer
插件時可選傳入browsers
參數,可以設置添加前綴的適配範圍,詳細可查閱browsers配置說明。
混合使用CSS預處理器與後處理器 - PreCSS
也許你迫不及待想在項目中引入postcss
,又希望能繼續使用CSS預處理器語法,而且需要保證以前按照某預處理器預定語法和模板(如SASS)編寫的源代碼繼續穩定使用,不需要太多的遷移和學習成本,可以做到嗎?當然可以,可以使用預處理器PreCSS
插件包,另外我們需要安裝一個postcss
的scss解析器,因爲postcss
默認只包含一個CSS解析器,postcss
配置文件更新如下:
module.exports = {
parser: require('postcss-scss'),
plugins: [
require('autoprefixer')({
browsers: ['last 2 versions']
}),
require('precss')
]
}
webpack配置文件更新配置:
modules: {
loaders: [
{
test: /\.s?[ac]ss$/,
exclude: /node_modules/,
// or 內聯方式 loader: 'style-loader!css-loader!postcss-loader'
loader: ExtractTextPlugin.extract({
fallback:'style-loader',
use: [
'css-loader',
'postcss-loader'
]
})
}
]
}
可以看到文件匹配規則,修改爲/\.s?[ac]ss$/
,可以匹配包括.sass, .scss, .css
樣式文件;在css-loader
加載器之前添加了postcss-loader
加載器(webpack加載器解析順序爲從右至左)。
當然你可以不使用precss
,依然使用sass-loader
,則只需要修改配置:
loader: 'style-loader!css-loader!postcss-loader!sass-loader'
對於如下SCSS代碼:
$column: 200px;
.menu {
display: flex;
width: calc(4 * $column);
}
轉換生成如下CSS代碼:
.menu {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
width: calc(4 * 200px);
}
處理圖片與字體文件
對於一個應用而言,除了需要開發HTML、CSS、JavaScript,通常還好使用到圖片,字體文件,配置文件等諸多資源,那麼前端工程化流程也就必然需要對這些資源進行處理與優化,最典型的說處理圖片和字體文件。
在Grunt或Gulp中,我們對圖片和字體文件的處理通常是將其從源目錄壓縮優化處理後輸出至輸出目錄,通常是以文件目錄整體進行處理,每次構建時,對所有資源,包括未使用的圖片均進行處理,效率是有侷限的;而webpack中一切資源文件都可以處理成模塊,然後在編譯時管理模塊依賴,可以做到只處理存在依賴的資源(即,使用了的資源)。
圖片與字體
當我們在Js模塊中引入CSS文件時,其中樣式規則中的背景圖片,字體文件如何處理呢?webpack只能管理模塊化的東西,需要將其模塊化,然後使用webpack管理依賴,webpack提供了file-loader
加載器:
File Loader
Instructs webpack to emit the required object as file and to return its public url. 通知webpack將引入的對象輸出爲文件並返回其公開資源路徑。
- 配置
module: {
loaders: [
{
test: /\.(png|svg|jpe?g|gif)$/,
loader: [
'file-loader'
]
}
]
}
- 說明
當我們在js文件中import Image from '../images/test.png'
或在CSS文件中url('../images/test.png')
時,file-loader
將處理該圖片並在output.path
目錄下輸出文件,然後將../images/test.png
路徑替換成該輸出文件路徑。
注,對於html中引用的圖片,需要使用[html-loader]加載器處理(http://npm.taobao.org/package/html-loader)。
-
參數
- emitFile: 是否輸出文件;
- name: 指定輸出文件的文件名,有幾個可用內置變量:
-
實例
在配置時可以指定參數:file-loader?name=[name].[ext]?[hash:8]
或者以配置對象方式:
{
test: /\.(png|svg|jpe?g|gif)$/,
loaders: [
// 'file-loader?name=[path][name].[ext]?[hash:8]'
{
loader: 'file-loader',
query: {
name: '[path][name].[ext]?[hash:8]'
}
}
]
}
對於CSS源代碼:
.wrapper {
font-size: 18px;
background: url('../images/test.png') no-repeat 0 0;
}
輸出CSS代碼如下:
.wrapper {
font-size: 18px;
background: url(assets/images/test.png?59427321) no-repeat 0 0;
}
assets
爲output.publicPath
指定值,images/test.png?59427321
爲配置文件中指定的name
模板,在output.path
目錄下輸出images/test.png
,區別是,不會攜帶?
後的參數。
另外,你也可以在js模板中這樣使用:
<img src={imgSrc} />
...
import imgSrc from 'path/xxx.png';
Url Loader
你可能會發現前面並沒有安裝file-loader
,因爲有更好用的加載器url-loader
,url-loader
加載器是file-loader
的升級版,他們唯一的不同之處在於:
url-loader
可以通過limit
參數指定一個尺寸值,加載器會對小於該值的資源處理返回一個Data URL,以base64的方式嵌入HTML或CSS,如url-loader?limit=65000
;對於大於該尺寸的資源將使用file-loader
處理並且傳遞所有參數。
- mimetype
還可以設置mimetype對處理文件進行過濾,如url-loader?mimetype=image/png
將只處理png類型的資源。
- 安裝
npm install --save-dev url-loader
- 配置
該加載器對於圖片和字體文件資源都適用:
{
test: /\.(png|svg|jpe?g|gif)$/,
loaders: [
// 'url-loader?name=[path][name].[ext]?[hash:8]'
{
loader: 'url-loader',
query: {
limit: 6000,
name: '[path][name].[ext]?[hash:8]'
}
}
]
}, {
test: /\.(woff|woff2|eot|ttf|otf)$/,
loaders: [{
loader: 'url-loader',
query: {
limit: 10000,
name: '[path][name].[ext]?[hash:8]'
}
}]
}
資源優化
完成以上配置後,已經可以在項目中很方便的引用各自資源了,但是通常我們還需要對圖片字體等文件進行壓縮優化處理,如Grunt中使用的imagemin插件一樣壓縮資源,webpack則提供了相關加載器img-loader
。
- 安裝
npm install --save-dev img-loader
- 配置
{
test: /\.(jpe?g|png|gif|svg)$/i,
loaders: [
'url-loader?name=[path][name].[ext]?[hash:8]',
{
loader: 'img-loader',
options: {
// 根據環境判斷是否啓用資源壓縮
enabled: process.env.NODE_ENV === 'production',
gifsicle: {
interlaced: false // 替換使用漸進式渲染
},
mozjpeg: {
progressive: true, // 創建基準jpeg文件
},
optipng: {
optimizationLevel: 4, // 優化級別,0-7,值越大,壓縮越多
},
pngquant: {
quality: '75-90', // 壓縮質量,0-100,值越高,質量越高
speed: 3 // 執行速度,0-10,速度過高質量受損,不建議過高
},
svgo: {
plugins: [
{ removeTitle: true }, // 去除標題信息
{ convertPathData: false } // 轉換路徑爲更短的相對或決定路徑
]
}
}
}
]
}
以上爲常見使用配置,更多詳細配置信息請查看對應說明imagemin文檔,特別注意的是上面使用了process.env.NODE_ENV
當前環境變量,只有在生產環境啓用圖片壓縮,因爲壓縮過程比較比較耗時,可能會降低開發、調試效率。
數據資源
對於數據類型文件資源,webpack內置支持加載解析.json
文件,而其他類型則需要安裝配置相應加載器,如.xml
文件,需要安裝並配置xml-loader
。
資源管理的思考
在傳統或稍早一點的應用中,我們通常會將所有的圖片,字體等資源放在一個基礎目錄下,如assets/
或images
,但是對於那些在多項目間重複的插件代碼或資源來說,每一次遷移,我們都得在一大堆圖片,字體資源裏尋找出我們需要遷移的資源,這對代碼可重用和其獨立性有一定限制,而且與現在提倡的組件化開發模式也不相符。
webpack對於資源的處理方式給組件化開發提供了很大便利,使得我們以組件爲單位,可以在某一組件目錄下存放所有相關的js,css,圖片,字體等資源文件;組件的遷移公用成本很低。不過組件化開發並不是說不需要資源目錄了,一些公用的資源依然放在項目的基礎目錄下。
說明
終於可以鬆口氣,對於webpack管理CSS、圖片、字體、數據資源的實踐基本總結完成,其實感覺要介紹的還有很多,但是要儘量保證文章思路清晰,語句流暢,而且篇幅不能太長,水平有限,花費較多時間經歷,希望能對讀者有所幫助,後續篇章也會繼續穿插介紹,力爭本系列能較完整、較清晰地描述如何使用webpack開發SPA應用。
原創文章,轉載請註明: 轉載自 熊建剛的博客
本文鏈接地址: webpack與SPA實踐之管理CSS等資源