淘先锋技术网

首页 1 2 3 4 5 6 7

浏览器在多次访问静态资源文件的时候,如果该文件已经在本地缓存,并且它的名称没有改变,那么浏览器会认为它没有被更新,便不会去请求服务器,而是使用该静态资源的缓存版本。如果某个文件被更改,我们希望浏览器应该重新请求它,而不是使用缓存版本,所以文件被修改后,它的名称也应该被改变。

webpack 中,最后生成的 chunk 文件都是由一个个的 module 编译后组合而成的,浏览器请求的 JS 文件实际上就是一个个的 chunk 。当我们修改某个 module 后,其所属 chunk 打包后的 JS 文件名称应该也要发生变化,否则浏览器是不会请求最新版本的 JS 文件的!

输出文件名

webpack 中,有 3 种哈希占位符,通过它们修改文件名来控制缓存。

1. hash:只要改动某个模块,所有 chunk 的 hash 都会被修改(不常用);

2. chunkhash:只要改动某个模块,依赖它的 chunk 的 hash 就会被修改(常用);

3. contenthash:只要改动某个模块,这个模块的 hash 就会被更改 ,通常用于被抽离的代码,如抽离 css 。如果不抽离该模块,contenthashchunkhash 的产生的效果一致 (常用,webpack3 不支持 contenthash

当 hash 变化后,文件名就会变化(如果输出文件名使用了上面的 3 中哈希占位符),浏览器就会去重新请求对应的资源。

[为了方便测试,我们打开 webpack 配置的 watch 选项。]

hash

准备测试的文件。

index1.js

console.log('index1.js');
import('./async').then((res) => {
    console.log(res);
})

index2.js

console.log('index2.js');
import('./async').then((res) => {
    console.log(res);
})

async.js

console.log('async.js');
export default  {
    desc: 'async.js'
}

webpack.config.js

var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
    CleanWebpackPlugin
} = require('clean-webpack-plugin');
module.exports = {
    mode: 'development',
    watch: true,
    entry: {
        index1: './index1.js',
        index2: './index2.js'
    },
    output: {
        publicPath: '',
        path: __dirname + '/dist',
        filename: '[name].[hash:16].js'
    },
    module: {
        rules: []
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './index.html'
        }),
    ]
}

编译后控制台打印如下。

                     Asset       Size  Chunks                         Chunk Names
     0.a535734c8f8ddf89.js  515 bytes       0  [emitted] [immutable]
                index.html  374 bytes          [emitted]
index1.a535734c8f8ddf89.js   8.54 KiB  index1  [emitted] [immutable]  index1
index2.a535734c8f8ddf89.js   8.53 KiB  index2  [emitted] [immutable]  index2

可以发现,所有 chunkhash 都是 a535734c8f8ddf89

修改 async.js、index1.js 或 index2.js 其中的某个文件都将导致所有 chunk 重新构建,这里修改 async.js 后,控制台打印如下。

                     Asset       Size  Chunks                         Chunk Names
     0.160f75be0b2a9d7a.js  519 bytes       0  [emitted] [immutable]
                index.html  374 bytes          [emitted]
index1.160f75be0b2a9d7a.js   8.54 KiB  index1  [emitted] [immutable]  index1
index2.160f75be0b2a9d7a.js   8.53 KiB  index2  [emitted] [immutable]  index2

可以发现,每次的改动都将导致整个项目重新构建。

chunkhash

webpack.config.js 中输出配置名称中的 hash 改为 chunkhash

webpack.config.js

var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
    CleanWebpackPlugin
} = require('clean-webpack-plugin');
module.exports = {
    mode: 'development',
    watch: true,
    entry: {
        index1: './index1.js',
        index2: './index2.js'
    },
    output: {
        publicPath: '',
        path: __dirname + '/dist',
        filename: '[name].[chunkhash:16].js'
    },
    module: {
        rules: []
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './index.html'
        }),
    ]
}

编译构建后,控制台打印如下。

                     Asset       Size  Chunks                         Chunk Names
     0.ef0a90ca4be41633.js  519 bytes       0  [emitted] [immutable]
                index.html  374 bytes          [emitted]
index1.e04fb78630b9ed06.js   8.55 KiB  index1  [emitted] [immutable]  index1
index2.a82fa8cf316672d3.js   8.54 KiB  index2  [emitted] [immutable]  index2

可以发现每个 chunk 都有自己的专属 hash,在修改 index1.js 或 index2.js 的时候,也只会导致被修改的文件所属的 chunk 重新构建。

修改 index1.js 后,控制台打印如下。

                     Asset       Size  Chunks                         Chunk Names
                index.html  374 bytes          [emitted]
index1.219644b9fa78ecf2.js   8.53 KiB  index1  [emitted] [immutable]  index1

修改 index2.js 后,控制台打印如下。

                     Asset       Size  Chunks                         Chunk Names
                index.html  374 bytes          [emitted]
index2.2fb396992e95ea30.js   8.53 KiB  index2  [emitted] [immutable]  index2

修改 async.js 后,控制台打印如下。

                     Asset       Size  Chunks                         Chunk Names
     0.ef0a90ca4be41633.js  519 bytes       0  [emitted] [immutable]
                index.html  374 bytes          [emitted]
index1.b19fb895cd7844da.js   8.53 KiB  index1  [emitted] [immutable]  index1
index2.89c07defcd285597.js   8.53 KiB  index2  [emitted] [immutable]  index2

index1.js 和 index2.js 所属的 chunk 重新构建了,这是因为 index1 chunk 和 index2 chunk 需要懒加载 async.js ,而懒加载 async.js 是根据 async.js 所属的 chunk 打包后的文件名来加载的,当 async.js 发生变化后,它所属的 chunk 打包后的文件名会因为 hash 变化而变化,index1 chunk 和 index2 chunk 中懒加载 相关的的路径名称也跟着发生了变化,从而导致 index1 chunk 和 index2 chunkhash 变化而重新构建。

因为懒加载流程是属于 runtime (安装、加载和连接模块的逻辑)部分的代码,所以可以将 runtime 部分的代码抽离出来,然后再次修改 async.js ,发现 index1 chunk 和 index2 chunk 的代码就不会重新构建了。

webpack.config.js

var HtmlWebpackPlugin = require('html-webpack-plugin');
var {
    CleanWebpackPlugin
} = require('clean-webpack-plugin');
module.exports = {
    mode: 'development',
    watch: true,
    entry: {
        index1: './index1.js',
        index2: './index2.js'
    },
    output: {
        publicPath: '',
        path: __dirname + '/dist',
        filename: '[name].[chunkhash:16].js'
    },
    module: {
        rules: []
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './index.html'
        }),
    ],
    optimization: {
        // 'single' - 表示抽离所有 chunk 公用的 runtime 代码
        runtimeChunk: 'single' 
    }
}

编译打包后,控制台打印如下。

                      Asset       Size   Chunks                         Chunk Names
      0.ef0a90ca4be41633.js  519 bytes        0  [emitted] [immutable]
                 index.html  425 bytes           [emitted]
 index1.ce2de40546b020f7.js  545 bytes   index1  [emitted] [immutable]  index1
 index2.cf1286e262093666.js  553 bytes   index2  [emitted] [immutable]  index2
runtime.a0f05e8cde7456ae.js   8.97 KiB  runtime  [emitted] [immutable]  runtime

修改 async.js 后,控制台打印如下。

                      Asset       Size   Chunks                         Chunk Names
      0.0f79481d25aeed80.js  523 bytes        0  [emitted] [immutable]
                 index.html  425 bytes           [emitted]
runtime.fa3fbcea2005af1c.js   8.97 KiB  runtime  [emitted] [immutable]  runtime

这下 index1.js 和 index2.js 对应的 chunk 便不会重新构建了,因为我们把连接模块的逻辑都抽离出来了,改变 async.js 会导致 async.js 和 runtime 代码对应的 chunk 重新构建。

contenthash

contenthash 只有在抽离某个 module 的时候才会显示它的作用,否则它和 chunkhash 的表现一致,这里以抽离 css 为例。先安装 css-loadermini-css-extract-plugin

yarn add css-loader mini-css-extract-plugin -d

index1.css

body , html {
    height: 100%;
    width: 100%;
    margin: 0;
    padding: 0;
}

index1.js

console.log('index1.js');
import('./async').then((res) => {
    console.log(res);
})
import './index1.css';

webpack.config.js

var HtmlWebpackPlugin = require('html-webpack-plugin');
var ProgressPlugin = require('webpack').ProgressPlugin;
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
var CleanWebpackPlugin = require('clean-webpack-plugin').CleanWebpackPlugin;
module.exports = {
    mode: 'development',
    watch: true,
    entry: {
        index1: './index1.js',
        index2: './index2.js'
    },
    output: {
        publicPath: '',
        path: __dirname + '/dist',
        filename: '[name].[chunkhash].js'
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: [
                MiniCssExtractPlugin.loader, // 抽取css文件的loader,不再插入head里
                'css-loader'
            ]
        }]
    },
    plugins: [
        new ProgressPlugin(),
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css'
        }),
        new HtmlWebpackPlugin({
            template: './index.html',
        })

    ],
    optimization: {
        // 'single' - 表示抽离所有 chunk 公用的 runtime 代码
        runtimeChunk: 'single'
    }
}

编译打包后,控制台打印如下。

                          Asset       Size   Chunks                         Chunk Names
      0.a84498019577afde7588.js   1.07 KiB        0  [emitted] [immutable]
                     index.html  499 bytes           [emitted]
 index1.470f6e18ca06585dd954.js   1.07 KiB   index1  [emitted] [immutable]  index1
index1.8db6dca1b242dbbabe70.css   87 bytes   index1  [emitted] [immutable]  index1
 index2.82d191b5754b0e2e2ca8.js  537 bytes   index2  [emitted] [immutable]  index2
runtime.fca50e4fde877e19576a.js   8.98 KiB  runtime  [emitted] [immutable]  runtime

修改 index1.js 后,控制台打印如下。

                         Asset       Size  Chunks                         Chunk Names
                    index.html  499 bytes          [emitted]
index1.2748748868607facc4a8.js   1.08 KiB  index1  [emitted] [immutable]  index1

index1.css 和 index1.js 都属于 index1 这个 chunk 。当修改 index1.js 后,并不会引起 indx1.css 重新构建,因为 index1.css 不依赖 index1.js 。contenthash 的作用就是:当模块自身发生变化的时候,才会去重新构建这个模块。

其他

在开发环境中,因为公用模块代码不常变,所以可以缓存它,在生产环境中也是一样。但是如果公用模块代码确实需要更新,则需要重新构建公用模块代码来更新它的文件名,让浏览器重新请求它,而我们发布的新版本的业务代码一定是不可以缓存的!