在前端,說到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
究竟是幹啥的!