傻傻分不清的Manifest

在前端,說到manifest,實際上是有歧義的,就我瞭解的狀況來講,manifest能夠指代下列含義:javascript

  1. html標籤的manifest屬性: 離線緩存(目前已被廢棄)
  2. PWA: 將Web應用程序安裝到設備的主屏幕
  3. webpack中webpack-manifest-plugin插件打包出來的manifest.json文件,用來生成一份資源清單,爲後端渲染服務
  4. webpack中DLL打包時,輸出的manifest.json文件,用來分析已經打包過的文件,優化打包速度和大小

下面咱們來一一介紹下css

html屬性

<!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-manifestnode

那如何更新緩存?通常有如下幾種方式:react

  • 用戶清空瀏覽器緩存
  • manifest 文件被修改(即便註釋被修改)
  • 由程序來更新應用緩存

須要特別注意:用戶第一次訪問該網頁,緩存文件以後,第二次進入該頁面,發現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

爲了實現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的react開發環境

本文默認你已經瞭解最基本的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環境搭建完成

webpack-manifest-plugin

一般狀況下,咱們打包出來的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.jsvendor.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沒有改變,這也是咱們但願的

DLL打包

上面的打包方式,隨着項目的複雜度上升後,打包速度會開始變慢。緣由是,每次打包,webpack都要分析哪些是公用庫,而後把他打包到vendor.js

咱們可不能夠在第一次構建vendor.js之後,下次打包,就直接跳過那些被打包到vendor.js裏的代碼呢?這樣打包速度能夠明顯提高

這就須要DllPlugin結合DllRefrencePlugin插件的運用

dll打包原理就是:

  1. 把指定的庫代碼打包到一個dll.js,同時生成一份對應的manifest.json文件
  2. webpack打包時,讀取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當作清單使用:

  1. 緩存清單
  2. PWA清單
  3. 打包資源路徑清單
  4. dll打包清單

只不過是在不一樣的場景中使用特定的清單來完成某些功能

因此,學好英文是多麼重要,這樣纔不會傻傻分不清manifest究竟是幹啥的!

相關文章
相關標籤/搜索