在前端,說到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開發環境
源代碼地址:https://github.com/deepred5/l...
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>
webpack中有運行時的概念,好比咱們經過webpack打包後分割成了dll.js
,vendors.js
,main.js
,那這三個代碼,到底哪一個先調用,哪一個後調用,他們運行順序就是由運行時代碼組織(經過讀取manifest
數據)
一般狀況下咱們無需關心運行時代碼,但若是但願儘量的優化瀏覽器緩存,那麼咱們能夠把運行時代碼單獨提取出來,這樣某些文件發生改變後,一些與之相關的文件hash值並不會也隨之改變。
經過配置runtimeChunk
便可
optimization: { runtimeChunk: { name: 'manifest' } }
咱們介紹了5種manifest
相關的前端技術。manifest
的英文含義是名單, 5種技術的確都是把manifest
當作清單使用:
只不過是在不一樣的場景中使用特定的清單來完成某些功能
因此,學好英文是多麼重要,這樣纔不會傻傻分不清manifest
究竟是幹啥的!