本篇文章會系統的介紹下 Webpack4 裏面資源內聯(HTML/CSS/JS/Image/Font)的正確姿式javascript
首先,咱們一塊兒瞭解下什麼是資源內聯。css
資源內聯(inline resource),就是將一個資源之內聯的方式嵌入進另外一個資源裏面,咱們經過幾個小例子來直觀感覺一下。html
HTML 內聯 CSS,這個其實就是咱們一般說的 內聯 CSS 或者 行內 CSS。咱們能夠寫幾行 reset CSS,而後經過 style
標籤的方式嵌入進了 HTML 裏面:前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
font-size: 12px;
font-family: Arial, Helvetica, sans-serif;
background: #fff;
}
ul, ol, li {
list-style-type: none;
}
</style>
</head>
<body>
</body>
</html>
複製代碼
CSS 內聯圖片,就是咱們一般將小圖片經過 base64 的方式內嵌進 CSS 裏面。咱們能夠將搜索小 icon 內聯進 CSS:java
// index.css
.search {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJ0lEQVQ4T6XSsUoEMRAG4H/ClZaLmbSW1pZ6+gAnFrK+gZXoK6jvIILgE6gIcnYWgmJno6AgYp1Z2EcIGQnsHbuaQ9abMkO+TGaGMGfQnPfxC3DOrajqPoB1AArgnohOvffPucc6ADMfAjgCUMYYH9MFY8wagEsAxyKScp2YAtbaERGNRST7LWZWVd2squq2LbSBMyK6E5GrXKnW2i1jzMh7v5sFmPkzhDCs69rngKIo3GAweBKRpVnAVwhh9Q/gRUQWs4Bz7jzGeFNV1ThXATOXAA5EJDV1Gr2aSETb3vvrLJAOmTmNKY2yVNUHVSVjzBDABYA3ADsi8j4TSIlmkfYAbABYUNUPACdE9NpAHaTXKjPz8k+kF9B8s4P0BibIpBf/AtpN/AYx54AR58WxmQAAAABJRU5ErkJggg==) no-repeat;
}
複製代碼
瞭解了資源內聯的基本概念後,可能你會問資源內聯有什麼意義?接下來咱們從幾個維度去看看爲何咱們須要資源內聯。node
資源內聯的意義這裏我從三個方面去說明一下,分別是:工程維護、頁面加載性能、頁面加載體驗。webpack
咱們看看資源內聯對於工程維護的意義,這個是一個基本的 HTML 結構。在現在流行的 Hybrid 混合開發架構裏,會有一個個的 H5 頁面,對應前端工程裏的多頁面應用(MPA)。git
咱們去打包多頁面應用的時候會藉助 html-webpack-plugin,每一個頁面會有一個 HTML 模板與之對應。每一個 HTML 模板都會包含不少類似的內容,好比 meta 信息,或 SSR 時須要用到的一些佔位符等等。試想一下,若是將下面這段 meta 代碼分別複製一份放到每一個 HTML 模板裏面將會對代碼維護形成的影響。github
<meta charset="UTF-8">
<meta name="viewport" content="viewport-fit=cover,width=device-width,initial-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="keywords" content="now,now直播,直播,騰訊直播,QQ直播,美女直播,附近直播,才藝直播,小視頻,我的直播,美女視頻,在線直播,手機直播">
<meta name="name" itemprop="name" content="NOW直播—騰訊旗下全民視頻社交直播平臺"><meta name="description" itemprop="description" content="NOW直播,騰訊旗下全民高清視頻直播平臺,聚集中外大咖,最in網紅,草根偶像,明星藝人,校花,小鮮肉,逗逼段子手,各種美食、音樂、旅遊、時尚、健身達人與你24小時不間斷互動直播,各類奇葩刺激的直播玩法,讓你躍躍欲試,你會發現,原來人人均可以當主播賺錢!">
<meta name="image" itemprop="image" content="https://pub.idqqimg.com/pc/misc/files/20170831/60b60446e34b40b98fa26afcc62a5f74.jpg"><meta name="baidu-site-verification" content="G4ovcyX25V">
<meta name="apple-mobile-web-app-capable" content="no">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<link rel="dns-prefetch" href="//11.url.cn/">
<link rel="dns-prefetch" href="//open.mobile.qq.com/">
複製代碼
這個時候推薦的作法是維護一份 meta.html,將上面的這個代碼內容放置進去。每一個 HTML 模板將 meta.html 片斷內聯進去。web
工程維護的另外一個比較常見的場景就是圖片、字體等文件的內聯了,好比不少同窗一般會去網上找一個在線的 base64 編碼工具(如:www.base64code.com/ )去將各類圖片(png、jpg、gif) 或者 字體 (ttf、otf) 編碼,而後將編碼後的那一長串字符串放置到代碼裏面去。好比前面的這個搜索 icon 圖標,這段長串的字符串放置在源代碼裏面根本毫無語義,並且對維護者而言也是場災難。
// index.css
.search {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJ0lEQVQ4T6XSsUoEMRAG4H/ClZaLmbSW1pZ6+gAnFrK+gZXoK6jvIILgE6gIcnYWgmJno6AgYp1Z2EcIGQnsHbuaQ9abMkO+TGaGMGfQnPfxC3DOrajqPoB1AArgnohOvffPucc6ADMfAjgCUMYYH9MFY8wagEsAxyKScp2YAtbaERGNRST7LWZWVd2squq2LbSBMyK6E5GrXKnW2i1jzMh7v5sFmPkzhDCs69rngKIo3GAweBKRpVnAVwhh9Q/gRUQWs4Bz7jzGeFNV1ThXATOXAA5EJDV1Gr2aSETb3vvrLJAOmTmNKY2yVNUHVSVjzBDABYA3ADsi8j4TSIlmkfYAbABYUNUPACdE9NpAHaTXKjPz8k+kF9B8s4P0BibIpBf/AtpN/AYx54AR58WxmQAAAABJRU5ErkJggg==) no-repeat;
}
複製代碼
咱們能夠經過更優雅的資源內聯語法來避免這個問題,文章後面會介紹到。
資源內聯的第2點意義在於能夠減小 HTTP 的請求數,固然若是你的網站有使用 HTTP2 這點的意義可能不會那麼大。將各類小圖片、小字體(好比:小於5k) 在生產環境 base64 到代碼裏面能夠極大的減小頁面的請求數量,從而提高頁面的加載時間。
資源內聯另一個重要的意義在於提高頁面加載體驗。咱們都知道瀏覽器解析 HTML源碼是從上到下解析,所以咱們會把 CSS 放到頭部,JS 放置到底部。以 SSR 場景爲例,若是不將打包出來的 CSS 內聯進 HTML 裏面,HTML 出來的時候頁面的結構已經有了,可是還須要發送一次請求去請求 css,這個時候就會出現頁面閃爍,網絡狀況差的時候更加明顯。
資源內聯的類型主要包含:
若是你曾經使用過 FIS 或者看過 FIS 的文檔,你會發現 FIS 對於資源內聯的支持很是棒,詳細的文檔:嵌入資源
FIS HTML 內聯 HTML 片斷:
<link rel="import" href="demo.html?__inline">
複製代碼
FIS HTML 內聯 JS 腳本:
<script type="text/javascript" src="demo.js?__inline"></script>
複製代碼
接下來,咱們分別看看每種內聯在 webpack4 中的實現。
HTML 內聯 HTML 片斷、CSS 或者 JS(babel 編譯後的,好比內聯某個 npm 組件) 的思路很簡單,就是直接讀取某個文件的內容,而後插入到對應的位置。咱們能夠藉助 raw-loader@0.5.1版本,最新的 raw-loader 會有問題(由於它導出模塊時是使用 export default),不過你徹底能夠本身實現這樣的一個 raw-loader。
0.5.1 版本的 raw-loader 的代碼:
module.exports = function(content) {
this.cacheable && this.cacheable();
this.value = content;
return "module.exports = " + JSON.stringify(content);
}
複製代碼
藉助 raw-loader 實現的內聯語法以下:
// 內聯 HTML 片斷
${ require('raw-loader!./meta.html')}
// 內聯 JS
<script>${ require('raw-loader!babel-loader!../../node_modules/lib-flexible/flexible.js')}</script>
複製代碼
咱們能夠實現一個對開發者更友好的語法糖,好比實現一個 loader 去解析 HTML 裏面的?__inline 語法。這裏我實現了一個 html-inline-loader,它的代碼以下:
const fs = require('fs');
const path = require('path');
const getContent = (matched, reg, resourcePath) => {
const result = matched.match(reg);
const relativePath = result && result[1];
const absolutePath = path.join(path.dirname(resourcePath), relativePath);
return fs.readFileSync(absolutePath, 'utf-8');
};
module.exports = function(content) {
const htmlReg = /<link.*?href=".*?\__inline">/gmi;
const jsReg = /<script.*?src=".*?\?__inline".*?>.*?<\/script>/gmi;
content = content.replace(jsReg, (matched) => {
const jsContent = getContent(matched, /src="(.*)\?__inline/, this.resourcePath); return `<script type="text/javascript">${jsContent}</script>`; }).replace(htmlReg, (matched) => { const htmlContent = getContent(matched, /href="(.*)\?__inline/, this.resourcePath);
return htmlContent;
});
return `module.exports = ${JSON.stringify(content)}`;
}
複製代碼
而後,你能夠這樣使用:
<!DOCTYPE html>
<html lang="en">
<head>
<link href="./meta.html?__inline">
<title>Document</title>
<script type="text/javascript" src="../../node_modules/lib-flexible/flexible.js?__inline"></script>
</head>
<body>
<div id="root"><!--HTML_PLACEHOLDER--></div>
<!--INITIAL_DATA_PLACEHOLDER-->
</body>
</html>
複製代碼
查看的效果:
一般狀況下,爲了更好的加載體驗,咱們會將打包好的 CSS 內聯到 HTML 頭部,這樣 HTML 加載完成 CSS 就能夠直接渲染出來,避免頁面閃動的狀況。那麼 CSS 內聯如何實現呢?
CSS 內聯的核心思路是:將頁面打包過程的產生的全部 CSS 提取成一個獨立的文件,而後將這個 CSS 文件內聯進 HTML head 裏面。這裏須要藉助 mini-css-extract-plugin 和 html-inline-css-webpack-plugin 來實現 CSS 的內聯功能。
// webpack.config.js
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js'
},
mode: 'production',
plugins: [
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css'
}),
new HtmlWebpackPlugin(),
new HTMLInlineCSSWebpackPlugin()
]
};
複製代碼
注:html-inline-css-webpack-plugin 須要放在 html-webpack-plugin 後面。
圖片和字體的內聯能夠藉助 url-loader,好比你能夠經過修改 webpack 配置讓小於 10k 的圖片或者字體文件在構建階段自動 base64。
// webpack.config.js
const path = require('path');
module.exports = {
entry: {
index: './src/index.js',
search: './src/search.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js'
},
mode: 'production',
module: {
rules: [
{
test: /.(png|jpg|gif|jpeg)$/,
use: [
{
loader: 'url-loader',
options: {
name: '[name]_[hash:8].[ext]',
limit: 10240
}
}
]
},
{
test: /.(woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'url-loader',
options: {
name: '[name]_[hash:8][ext]',
limit: 10240
}
}
]
}
]
}
};
複製代碼
不過 url-loader 作資源內聯最大的缺陷就是 不能個性化的去設置某張圖片自動編碼,針對這個問題,咱們能夠借鑑下 FIS 的語法糖,實現 ?__inline 的語法糖,引用某個圖片的時候看到這個後綴則自動的將這張圖片進行 base64 編碼。這個功能實現起來也很簡單,能夠參考我實現的 inline-file-loader,核心代碼:
export default function loader(content) {
const options = loaderUtils.getOptions(this) || {};
validateOptions(schema, options, {
name: 'File Loader',
baseDataPath: 'options',
});
const hasInlineFlag = /\?__inline$/.test(this.resource);
if (hasInlineFlag) {
const file = this.resourcePath;
// Get MIME type
const mimetype = options.mimetype || mime.getType(file);
if (typeof content === 'string') {
content = Buffer.from(content);
}
return `module.exports = ${JSON.stringify(
`data:${mimetype || ''};base64,${content.toString('base64')}`
)}`;
}
複製代碼
有了圖片的內聯功能,咱們能夠將前面的搜索 icon 圖標內聯的寫法修改爲:
// index.css
.search {
background: url(./search-icon.png?__inline) no-repeat;
}
複製代碼
下面是本篇文章的代碼演示資料,若是有需求,能夠自行獲取。
個人我的博客:github.com/cpselvis/bl…