在前端,说到manifest
,实际上是有歧义的,就我了解的状况来讲,manifest
能够指代下列含义:javascript
html
标签的manifest
属性: 离线缓存(目前已被废弃)manifest.json
文件,用来生成一份资源清单,为后端渲染服务manifest.json
文件,用来分析已经打包过的文件,优化打包速度和大小下面咱们来一一介绍下css
<!DOCTYPE html>
<html lang="en" manifest="/tc.mymanifest">
<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>
<link rel="stylesheet" href="/theme.css">
<script src="/main.js"></script>
<script src="/main2.js"></script>
</head>
<body>
</body>
</html>
复制代码
浏览器解析这段html标签时,就会去访问tc.mymanifest
这个文件,这是一个缓存清单文件html
tc.mymanifest
前端
# v1 这是注释
CACHE MANIFEST
/theme.css
/main.js
NETWORK:
*
FALLBACK:
/html5/ /404.html
复制代码
CACHE MANIFEST
指定须要缓存的文件,第一次下载完成之后,文件都不会再从网络请求了,即便用户不是离线状态,除非tc.mymanifest
更新了,缓存清单更新以后,才会再次下载。标记了manifest的html自己也被缓存vue
NETWORK
指定非缓存文件,全部相似资源的请求都会绕过缓存,即便用户处于离线状态,也不会读缓存html5
FALLBACK
指定了一个后备页面,当资源没法访问时,浏览器会使用该页面。 好比离线访问/html5/目录时,就会用本地的/404.html页面java
缓存清单能够是任意后缀名,不过必须指定content-type
属性为text/cache-manifest
node
那如何更新缓存?通常有如下几种方式:react
须要特别注意:用户第一次访问该网页,缓存文件以后,第二次进入该页面,发现tc.mymanifest
缓存清单更新了,因而会从新下载缓存文件,可是,第二次进入显示的页面仍然执行的是旧文件,下载的新文件,只会在第三次进入该页面后执行!!!android
若是但愿用户当即看到新内容,须要js监听更新事件,从新加载页面
window.addEventListener('load', function (e) {
window.applicationCache.addEventListener('updateready', function (e) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
// 更新缓存
// 从新加载
window.applicationCache.swapCache();
window.location.reload();
} else {
}
}, false);
}, false);
复制代码
建议对tc.mymanifest
缓存清单设置永不缓存
不过,manifest也有不少缺点,好比须要手动一个个填写缓存的文件,更新文件以后须要二次刷新,若是更新的资源中有一个资源更新失败了,将致使所有更新失败,将用回上一版本的缓存
HTML5规范也废弃了这个属性,所以不建议使用
为了实现PWA应用添加至桌面的功能,除了要求站点支持HTTPS以外,还须要准备 manifest.json
文件去配置应用的图标、名称等信息
<link rel="manifest" href="/manifest.json">
复制代码
{
"name" : "Minimal PWA" ,
"short_name" : "PWA Demo" ,
"display" : "standalone" ,
"start_url" : "/" ,
"theme_color" : "#313131" ,
"background_color" : "#313131" ,
"icons" : [
{
"src": "images/touch/homescreen48.png",
"sizes": "48x48",
"type": "image/png"
}
]
}
复制代码
经过一系列配置,就能够把一个PWA像APP同样,添加一个图标到手机屏幕上,点击图标便可打开站点
本文默认你已经了解最基本的webpack配置,若是彻底不会,建议看下这篇文章
咱们首先搭建一个最简单的基于webpack的react开发环境
源代码地址:github.com/deepred5/le…
mkdir learn-dll
cd learn-dll
复制代码
安装依赖
npm init -y
npm install @babel/polyfill react react-dom --save
复制代码
npm install webpack webpack-cli webpack-dev-server @babel/core @babel/preset-env @babel/preset-react add-asset-html-webpack-plugin autoprefixer babel-loader clean-webpack-plugin css-loader html-webpack-plugin mini-css-extract-plugin node-sass postcss-loader sass-loader style-loader --save-dev
复制代码
新建.bablerc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage", // 根据browserslis填写的浏览器,自动添加polyfill
"corejs": 2,
}
],
"@babel/preset-react" // 编译react
],
"plugins": []
}
复制代码
新建postcss.config.js
module.exports = {
plugins: [
require('autoprefixer') // 根据browserslis填写的浏览器,自动添加css前缀
]
}
复制代码
新建.browserslistrc
last 10 versions
ie >= 11
ios >= 9
android >= 6
复制代码
新建webpack.dev.js
(基本配置再也不详细介绍)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].js',
chunkFilename: '[name].chunk.js',
},
devServer: {
historyApiFallback: true,
overlay: true,
port: 9001,
open: true,
hot: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: ['style-loader',
'css-loader',
'postcss-loader'
],
},
{
test: /\.scss$/,
use: ['style-loader',
{
loader: 'css-loader',
options: {
modules: false,
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
],
},
]
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }), // index打包模板
]
}
复制代码
新建src
目录,并新建src/index.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>learn dll</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
复制代码
新建src/Home.js
import React from 'react';
import './Home.scss';
export default () => <div className="home">home</div>
复制代码
新建src/Home.scss
.home {
color: red;
}
复制代码
新建src/index.js
import React, { Component } from 'react';
import ReactDom from 'react-dom';
import Home from './Home';
class Demo extends Component {
render() {
return (
<Home /> ) } } ReactDom.render(<Demo/>, document.getElementById('app')); 复制代码
修改package.json
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js"
},
复制代码
最后,运行npm run dev
,应该能够看见效果
新建webpack.prod.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'production',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, // 单独提取css文件
'css-loader',
'postcss-loader'
],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false,
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
],
},
]
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[id].[contenthash:8].css',
}),
new CleanWebpackPlugin(), // 打包前先删除以前的dist目录
]
};
复制代码
修改package.json
,添加一句"build": "webpack --config webpack.prod.js"
运行npm run build
,能够看见打包出来的dist
目录
html,js,css都单独分离出来了
至此,一个基于webpack的react环境搭建完成
一般状况下,咱们打包出来的js,css都是带上版本号的,经过HtmlWebpackPlugin
能够自动帮咱们在index.html
里面加上带版本号的js和css
<!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>learn dll</title>
<link href="main.198b3634.css" rel="stylesheet"></head>
<body>
<div id="app"></div>
<script type="text/javascript" src="main.d312f172.js"></script></body>
</html>
复制代码
可是在某些状况,index.html
模板由后端渲染,那么咱们就须要一份打包清单,知道打包后的文件对应的真正路径
安装插件webpack-manifest-plugin
npm i webpack-manifest-plugin -D
修改webpack.prod.js
const ManifestPlugin = require('webpack-manifest-plugin');
module.exports = {
// ...
plugins: [
new ManifestPlugin()
]
};
复制代码
从新打包,能够看见dist
目录新生成了一个manifest.json
{
"main.css": "main.198b3634.css",
"main.js": "main.d312f172.js",
"index.html": "index.html"
}
复制代码
好比在SSR开发时,前端打包后,node后端就能够经过这个json数据,返回正确资源路径的html模板
const buildPath = require('./dist/manifest.json');
res.send(` <!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>ssr</title> <link href="${buildPath['main.css']}" rel="stylesheet"></head> <body> <div id="app"></div> <script type="text/javascript" src="${buildPath['main.js']}"></script></body> </html> `);
复制代码
咱们以前的打包方式,有一个缺点,就是把业务代码和库代码都通通打到了一个main.js
里面。每次业务代码改动后,main.js
的hash值就变了,致使客户端又要从新下载一遍main.js
,可是里面的库代码实际上是没改变的!
一般状况下,react
react-dom
之类的库,都是不常常改动的。咱们但愿单独把这些库代码提取出来,生成一个vendor.js
,这样每次改动代码,只是下载main.js
,vendor.js
能够充分缓存(也就是所谓的代码分割code splitting)
webpack4自带代码分割功能,只要配置:
optimization: {
splitChunks: {
chunks: 'all'
}
}
复制代码
webpack.prod.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
module.exports = {
mode: 'production',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false,
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
],
},
]
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[id].[contenthash:8].css',
}),
new CleanWebpackPlugin(),
new ManifestPlugin()
],
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
复制代码
从新打包,发现新生成了一个vendor.js
文件,公用的一些代码就被打包进去了
从新修改src/Home.js
,而后打包,你会发现vendor.js
的hash没有改变,这也是咱们但愿的
上面的打包方式,随着项目的复杂度上升后,打包速度会开始变慢。缘由是,每次打包,webpack都要分析哪些是公用库,而后把他打包到vendor.js
里
咱们可不能够在第一次构建vendor.js
之后,下次打包,就直接跳过那些被打包到vendor.js
里的代码呢?这样打包速度能够明显提高
这就须要DllPlugin
结合DllRefrencePlugin
插件的运用
dll打包原理就是:
dll.js
,同时生成一份对应的manifest.json
文件manifest.json
,知道哪些代码能够直接忽略,从而提升构建速度咱们新建一个webpack.dll.js
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
vendors: ['react', 'react-dom'] // 手动指定打包哪些库
},
output: {
filename: '[name].[hash:8].dll.js',
path: path.resolve(__dirname, './dll'),
library: '[name]'
},
plugins: [
new CleanWebpackPlugin(),
new webpack.DllPlugin({
path: path.join(__dirname, './dll/[name].manifest.json'), // 生成对应的manifest.json,给webpack打包用
name: '[name]',
}),
],
}
复制代码
添加一条命令:
"build:dll": "webpack --config webpack.dll.js"
运行dll打包
npm run build:dll
发现生成一个dll
目录
修改webpack.prod.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const webpack = require('webpack');
module.exports = {
mode: 'production',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false,
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
],
},
]
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[id].[contenthash:8].css',
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dll/vendors.manifest.json') // 读取dll打包后的manifest.json,分析哪些代码跳过
}),
new CleanWebpackPlugin(),
new ManifestPlugin()
],
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
复制代码
从新npm run build
,发现dist
目录里,vendor.js
没有了
这是由于react
,react-dom
已经打包到dll.js
里了,webpack
读取manifest.json
以后,知道能够忽略这些代码,因而就没有再打包了
但这里还有个问题,打包后的index.html
还须要添加dll.js
文件,这就须要add-asset-html-webpack-plugin
插件
npm i add-asset-html-webpack-plugin -D
修改webpack.prod.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const webpack = require('webpack');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].[contenthash:8].js',
chunkFilename: '[name].[contenthash:8].chunk.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false,
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
],
},
]
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new AddAssetHtmlPlugin({ filepath: path.resolve(__dirname, './dll/*.dll.js') }), // 把dll.js加进index.html里,而且拷贝文件到dist目录
new MiniCssExtractPlugin({
filename: '[name].[contenthash:8].css',
chunkFilename: '[id].[contenthash:8].css',
}),
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dll/vendors.manifest.json') // 读取dll打包后的manifest.json,分析哪些代码跳过
}),
new CleanWebpackPlugin(),
new ManifestPlugin()
],
optimization: {
splitChunks: {
chunks: 'all'
}
}
};
复制代码
从新npm run build
,能够看见dll.js
也被打包进dist
目录了,同时index.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>learn dll</title>
<link href="main.198b3634.css" rel="stylesheet"></head>
<body>
<div id="app"></div>
<script type="text/javascript" src="vendors.8ec3d1ea.dll.js"></script><script type="text/javascript" src="main.0bc9c924.js"></script></body>
</html>
复制代码
咱们介绍了4种manifest
相关的前端技术。manifest
的英文含义是名单, 4种技术的确都是把manifest
当作清单使用:
只不过是在不一样的场景中使用特定的清单来完成某些功能
因此,学好英文是多么重要,这样才不会傻傻分不清manifest
究竟是干啥的!