专题课程中没有对CSS内联进行演示。 明天我会系统介绍Webpack4上资源内联(HTML/CSS/JS/Image/Font)的正确姿势!
首先,我们来了解一下什么是资源内联。
什么是内联资源?
资源内联(inlineresource)就是将一个资源以内联的形式嵌入到另一个资源中。 我们通过几个小反例来直观地理解一下。
HTML内联CSS,这显然就是我们一般所说的内联CSS或者内联CSS。 我们可以写几行resetCSS,然后通过style标签嵌入到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中。 我们可以将搜索图标内联到 CSS 中:
.search {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJ0lEQVQ4T6XSsUoEMRAG4H/ClZaLmbSW1pZ6+gAnFrK+gZXoK6jvIILgE6gIcnYWgmJno6AgYp1Z2EcIGQnsHbuaQ9abMkO+TGaGMGfQnPfxC3DOrajqPoB1AArgnohOvffPucc6ADMfAjgCUMYYH9MFY8wagEsAxyKScp2YAtbaERGNRST7LWZWVd2squq2LbSBMyK6E5GrXKnW2i1jzMh7v5sFmPkzhDCs69rngKIo3GAweBKRpVnAVwhh9Q/gRUQWs4Bz7jzGeFNV1ThXATOXAA5EJDV1Gr2aSETb3vvrLJAOmTmNKY2yVNUHVSVjzBDABYA3ADsi8j4TSIlmkfYAbABYUNUPACdE9NpAHaTXKjPz8k+kF9B8s4P0BibIpBf/AtpN/AYx54AR58WxmQAAAABJRU5ErkJggg==) no-repeat;
}
了解了资源内联的基本概念后,你可能会问资源内联的意义是什么? 接下来我们从几个维度来看看为什么需要资源内联。
资源内联的含义
这里我将从工程维护、页面加载性能、页面加载体验三个方面来阐述资源内联的意义。
工程维护
我们来看看资源内联对于项目维护的意义。 这是一个基本的 HTML 结构。 现在流行的Hybrid开发架构中,都会有一张一张的H5页面webpack 页面图片,对应后端项目中的多页面应用(MPA)。
当我们打包多页面应用时,我们会使用html-webpack-plugin,每个页面都会有一个与之对应的HTML模板。 每个HTML模板还包含很多类似的内容,比如元信息,或者SSR需要的一些占位符等。试想一下,如果将下面的元代码复制到每个HTML模板上,对代码维护的影响。
<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 片段。
工程维护中另一个常见的场景是图片、字体等文件的内联。 例如,很多朋友通常会去网上找一个在线base64编码工具(如:)来转换各种图片(png、jpg、gif)或字体(ttf、otf)编码,然后将编码后的长字符串放在代码上。 比如上面的搜索图标,这个长字符串放在源码上根本就没有任何语义,对于维护者来说也是一场灾难。
// index.css
.search {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJ0lEQVQ4T6XSsUoEMRAG4H/ClZaLmbSW1pZ6+gAnFrK+gZXoK6jvIILgE6gIcnYWgmJno6AgYp1Z2EcIGQnsHbuaQ9abMkO+TGaGMGfQnPfxC3DOrajqPoB1AArgnohOvffPucc6ADMfAjgCUMYYH9MFY8wagEsAxyKScp2YAtbaERGNRST7LWZWVd2squq2LbSBMyK6E5GrXKnW2i1jzMh7v5sFmPkzhDCs69rngKIo3GAweBKRpVnAVwhh9Q/gRUQWs4Bz7jzGeFNV1ThXATOXAA5EJDV1Gr2aSETb3vvrLJAOmTmNKY2yVNUHVSVjzBDABYA3ADsi8j4TSIlmkfYAbABYUNUPACdE9NpAHaTXKjPz8k+kF9B8s4P0BibIpBf/AtpN/AYx54AR58WxmQAAAABJRU5ErkJggg==) no-repeat;
}
我们可以通过更加高贵的资源内嵌句型来避免这个问题,这将在文章接下来介绍。
页面加载性能
资源内联的第二个意义是可以减少HTTP请求的数量。 事实上,如果您的网站使用 HTTP2,这可能就不那么重要了。 在生产环境中将各种小图片、小字体(例如大于5k)Base64到代码中,可以大大减少页面请求次数,从而提高页面加载时间。
页面加载体验
资源内联的另一个重要意义是改善页面加载体验。 我们都知道浏览器是从上到下解析HTML源代码的,所以我们会把CSS放在后面,JS放在上面。 以SSR场景为例,如果打包的CSS没有内嵌到HTML中,那么下载HTML时页面的结构就已经有了webpack 页面图片,需要发送请求来请求CSS。 只有这样页面才会闪烁。 ,当网络状况较差时,这种现象更加明显。
资源内联类型
资源内联的类型主要包括:
如果您以前使用过FIS或者阅读过FIS文档,您会发现FIS对资源内联有极好的支持。 详细文档:嵌入资源
FISHTML 内联 HTML 片段:
<link rel="import" href="demo.html?__inline">
FISHTML 内联 JS 脚本:
<script type="text/javascript" src="demo.js?__inline"></script>
接下来我们看一下webpack4中各类内联的实现。
HTML 内联基础知识
HTML内联HTML片段、CSS或者JS(Babel编译的,比如内联一个npm组件)的思路很简单,就是直接读取一个文件的内容,然后插入到对应的位置。 我们可以使用raw-loader@0.5.1版本。 最新的raw-loader会有问题(因为导入模块时使用的是exportdefault句型),但是你可以自己实现这样的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-webpack-plugin提供的):
// 内联 HTML 片段
${ require('raw-loader!./meta.html')}
// 内联 JS
${ 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 = //gmi;
const jsReg = /.*?/gmi;
content = content.replace(jsReg, (matched) => {
const jsContent = getContent(matched, /src="(.*)?__inline/,%20this.resourcePath);
return `${jsContent}`;
}).replace(htmlReg, (matched) => {
const htmlContent = getContent(matched, /href="(.*)?__inline/,%20this.resourcePath);
return htmlContent;
});
return `module.exports = ${JSON.stringify(content)}`;
}
之后,您可以像这样使用它:
<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"></div>
</body>
</html>
看看效果:
CSS 内联
一般情况下,为了更好的加载体验,我们会将打包好的CSS内联到HTML后面,这样HTML加载完之后就可以直接渲染CSS,防止页面闪烁。 如何实现CSS内联?
CSS内联的核心思想是将页面打包过程中形成的所有CSS提取到一个单独的文件中,然后将这个CSS文件内联到HTML头中。 这里需要使用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 配置更改为手动使用 Base64 图像或大于 10k 的字体文件。
webpack.config.js
const path = require('path');
{ =
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')}`
)}`;
}
利用images的内联功能,我们可以将上面搜索图标的内联写法改为:
// index.css
.search {
background: url(./search-icon.png?__inline) no-repeat;
}
终于
下面是本文的代码演示材料。 如果你需要的话,你可以自己去拿。