关于Webpack中hash的用法

时间: 2017-12-01阅读: 1304标签: webpack

webpack的配置项中,可能会见到hash这样的字符。

当存在hash配置的时候,webpack的输出将可以得到形如这样的文件:

page1_bundle_54e8c56e.js

这种带哈希值的文件名,可以帮助实现静态资源的长期缓存,在生产环境中非常有用。关于这一点的详细内容,可以参考这篇久远的大公司里怎样开发和部署前端代码

在webpack中配置hash

下面是一个带hash输出的webpack配置的例子(webpack v3.0.0):

var env = {
    src: path.resolve(__dirname, './src'),
    output: path.resolve(__dirname, './dist'),
    publicPath: '/'
};

module.exports = {
    entry: {
        'page1': './page1',
        'page2': './page2'
    },
    context: env.src,
    output: {
        path: env.output,
        filename: './[name]/bundle_[chunkhash:8].js',
        publicPath: env.publicPath
    },
    devtool: false,
    module: {
        rules: [{
            test: /\.(png|jpg)$/,
            use: 'url-loader?limit=8192&name=[path][name]_[hash:8].[ext]'
        }, {
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
                fallback: 'style-loader',
                use: 'css-loader'
            })
        }]
    },
    plugins: [
        new ExtractTextPlugin({
            filename: './[name]/style_[contenthash:8].css'
        })
    ]
};

可以看到,有多个地方都出现了hash这个词,但形式不太一样。

output的情况

output的filename可以指定hash。有两个值可以选择:

  • [hash]。hash值是特定于整个构建过程的。
  • [chunkhash]。hash值是特定于每一个文件的内容的。

我们理想的缓存设计是,在一次版本更新(重新构建)后,只有当一个文件的内容确实发生了变化,它才需要被重新下载,否则应使用缓存。

因此,以上两个值中更推荐的是[chunkhash]。你也可以阅读这篇官方的缓存指南了解更多细节。

file-loader的情况

url-loader和file-loader是同一家,参照file-loader文档可知,文件名name可以使用标识符[hash]来启用hash。此外,你还可以按照[<hashType>:hash:<digestType>:<length>]的格式更详细地定制hash结果。

[hash:8]中的:8则和前面output的一样,指定了hash结果的截取长度。

extract-text-webpack-plugin的情况

被引用的css通过extract-text-webpack-plugin来得到带hash的文件。参照extract-text-webpack-plugin文档,在指定生成文件的文件名filename时可以使用标识符[contenthash](可以看到,和之前的并不相同)。

引用带hash的文件

当静态资源的文件名变成这样的带哈希值的版本后,引用这些静态资源就需要稍多花一点工夫。

纯前端的情况

如果没有任何服务端,只是纯html、css、js的前端应用的话,一般使用html-webpack-plugin

例如,新建一个index.ejs模板文件如下:

<!DOCTYPE html>
<html>

<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>App Example</title>
</head>

<body>
    <main id="root"></main>
</body>

</html>

然后增加html-webpack-plugin到webpack:

{
  plugins: [
    new HtmlWebpackPlugin({
      template: 'index.ejs'
    })
  ]
}

执行一次webpack构建,得到生成的index.html:

<!DOCTYPE html>
<html>

<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>App Example</title>
    <link href="/page1/style_626f7c3f.css" rel="stylesheet">
</head>

<body>
    <main id="root"></main>
    <script type="text/javascript" src="/page1/bundle_0f33bdc8.js"></script>
</body>

</html>

可以看到,html-webpack-plugin在模板文件内容的基础上,就添加好了需要引用的bundle js。如果还有生成的css文件(通过extract-text-webpack-plugin),也会被添加到适当的位置。

纯前端、多页的情况

如果webpack有多个entry文件,例如本文最前面给出的例子:

{
    entry: {
        'page1': './page1',
        'page2': './page2'
    }
}

在这种情况下,html-webpack-plugin会把全部entry的输出都集中到一个.html里。所以,这可能并不是我们想要的。

我们更希望的是为每一个entry生成一个.html。这时候,可以使用的是multipage-webpack-plugin。这个插件实际也依赖了html-webpack-plugin。

例如,有这样的目录结构:

.
├─ package.json
├─ src
│   ├─ page1
│   │   ├─ index.css
│   │   ├─ index.ejs
│   │   ├─ index.js
│   │   └─ potofu.jpg
│   └─ page2
│       ├─ index.css
│       ├─ index.ejs
│       └─ index.js
└─ webpack.config.js

然后在webpack配置文件中加入multipage-webpack-plugin:

{
  plugins: [
    new MultipageWebpackPlugin({
        htmlTemplatePath: '[name]/index.ejs',   // 源模板文件的位置
        bootstrapFilename: 'manifest.js',
        templatePath: '[name]'  // 输出html文件的路径
    }),
  ]
}

[name]标识符对应的是每一个entry的名称(注意,在本文的时间点,需要使用multipage-webpack-plugin的master分支,也就是最新版,才支持此标识符)。在这个例子中,只有两个取值:page1,page2。

bootstrapFilename如字面意义,是指保存webpack的bootstrap代码的文件命名。而webpack的bootstrap代码被这样单独放到一个文件里,是因为multipage-webpack-plugin在内部(强行)为你启用了CommonsChunkPlugin。

执行一次webpack构建,得到的输出结果:

dist
├─ manifest.js
├─ page1
│   ├─ bundle_29862ad6.js
│   ├─ index.html
│   ├─ potofu_26766d43.jpg
│   └─ style_0b5ab6ef.css
├─ page2
│   ├─ bundle_6a9c6f12.js
│   ├─ index.html
│   └─ style_914dffd0.css
└─ shared
    └─ bundle_9fa1a762.js

取其中一个page1/index.html,内容是:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>page1</title>
    <link href="/page1/style_0b5ab6ef.css" rel="stylesheet">
</head>

<body>
    <div class="page-box">page1</div>
    <script type="text/javascript" src="/manifest.js"></script>
    <script type="text/javascript" src="/shared/bundle_9fa1a762.js"></script>
    <script type="text/javascript" src="/page1/bundle_29862ad6.js"></script>
</body>

</html>

可以看到,关联的css、js静态资源,都已被正确添加。

带服务端的情况

如果是带服务端的应用,引用带hash的资源文件将是另一个思路。

常见的做法是,为所有的静态资源生成一个.json清单文件,然后在服务端读取这个.json,然后把清单信息提供给模板文件,由此来正确地引用所需的静态资源

插件webpack-manifest-pluginassets-webpack-plugin都可以帮助完成这一点。

服务端例子 - Spring Boot & Thymeleaf

请看一个Spring Boot(1.5.3.RELEASE) & Thymeleaf(2.1)的例子。这里选择webpack-manifest-plugin。

首先,在webpack的配置中加入这个插件:

{
  plugins: [
     new ManifestPlugin()
  ]
}

执行webpack构建,即生成一个资源清单文件manifest.json(位置取决于webpack的output配置,这里是src/main/resources/static),它的内容是这样:

{
  "account/login.css": "account/login_style_f549ea0a.css",
  "account/login.js": "account/login_bundle_279af402.js"
}

接下来,创建一个帮助类ResourceFormatter(名称自拟):

public class ResourceFormatter{

    private JsonNode resourceMap;

    public ResourceFormatter(){
        ObjectMapper mapper = new ObjectMapper();
        Resource resource = new ClassPathResource("static/manifest.json");

        try {
            resourceMap = mapper.readValue(resource.getFile(), JsonNode.class);
        } catch (IOException e) {
            resourceMap = null;
        }
    }

    public String format(String originPath){

        if(resourceMap != null && resourceMap.has(originPath)){
            return "/" + resourceMap.get(originPath).asText();
        }

        return "/" + originPath;
    }
}

这个帮助类在初始化的时候就会读取manifest.json,而在format()方法里则会利用清单信息对路径进行转换。

然后,把这个帮助类添加到模板引擎Thymeleaf内,包含两步。

第一步,创建一个Dialect类:

public class ResourceDialect extends AbstractDialect implements IExpressionEnhancingDialect {

    public ResourceDialect() {
        super();
    }

    @Override
    public String getPrefix() {
        return "resource";
    }

    @Override
    public Map<String, Object> getAdditionalExpressionObjects(IProcessingContext processingContext) {
        Map<String, Object> expressions = new HashMap<>();
        expressions.put("resourceFormatter", new ResourceFormatter());
        return expressions;
    }
}

可以看到ResourceFormatter在这里被实例化并添加。

第二步,在Spring应用中注册这个Dialect类:

@Configuration
public class ThymeleafConfig {
    @Bean
    public ResourceDialect resourceDialect() {
        return new ResourceDialect();
    }
}

到此,就可以在Thymeleaf视图模板文件中使用了。修改视图文件如下(只包含修改的部分):

<link rel="stylesheet" th:href="@{${#resourceFormatter.format('account/login.css')}}" th:unless="${@environment.acceptsProfiles('dev')}" />
<!-- ... -->
<script th:src="@{${#resourceFormatter.format('account/login.js')}}"></script>

最后,启动服务,访问该页,可以看到最终的输出信息:

<link rel="stylesheet" href="/account/login_style_f549ea0a.css">
<!-- ... -->
<script src="/account/login_bundle_279af402.js"></script>

这就是我们要的带hash的文件了。

此外,关于如何在Spring Boot中引入webpack,可以参考这个spring-boot-angular2-seed

服务端例子 - Koa

看完了一个传统Java应用的例子,再来看看现代的Node应用。Koa(v2)是简洁的Node服务端框架,在它的基础上引用带hash的资源文件,也是同样的思路。

首先,同样是在webpack配置中加入webpack-manifest-plugin。

运行webpack构建生成manifest.json,内容大概会像这样:

{
  "page1.css": "page1/style_0b5ab6ef.css",
  "page1.js": "page1/bundle_0f33bdc8.js",
  "page1\\potofu.jpg": "page1/potofu_26766d43.jpg"
}

然后,读取这个json,为Koa(通过ctx.state)添加一个资源路径转换的帮助方法:

import manifest from './public/manifest.json';

app.use(async(ctx, next) => {
    ctx.state.resourceFormat = (originPath) => {

        if (originPath in manifest) {
            return "/" + manifest[originPath];
        }

        return "/" + originPath;
    };
    await next();
});

最后,在视图模板(这里的模板引擎是ejs)内,引用所需的静态资源:

<link rel="stylesheet" href="<%= resourceFormat('page1.css') %>">
<!-- ... -->
<script src="<%= resourceFormat('page1.js') %>"></script>

到此,Koa的例子就完成了。

结语

带hash的文件是现在web启用缓存来提升性能比较建议的形式,如果你也有类似的生产环境优化的需要,很推荐你也试试。

原文地址:http://acgtofe.com 

webpack—url-loader 解决项目中图片打包路径问题

刚开始用webpack的同学很容易掉进图片打包这个坑里,比如打包出来的图片地址不对或者有的图片并不能打包进我们的目标文件夹里。下面我们就来分析下在webpack项目中图片的应用场景。

基于 React & TS & Webpack 的微前端应用模板

在 Web 开发导论/微前端与大前端一文中,笔者简述了微服务与微前端的设计理念以及微前端的潜在可行方案。微服务与微前端,都是希望将某个单一的单体应用,转化为多个可以独立运行、独立开发、独立部署、独立维护的服务或者应用的聚合

Vue -- webpack 项目自动打包压缩成zip文件

这段时间用 Vue2.0 开发项目,每次打包都会用到 npm run build 命令,但是每次部署时给后端发包都要手动zip压缩,这样一两次还行,但遇到项目板块测试和临时加急功能测试的时候,一天可能就要打包好多次,这就很烦了。

在webpack自定义配置antd的按需加载和修改主题色

最近使用antd来做react项目的UI。从antd官网上,在使用create-react-app脚手架搭建项目时步骤如下:加模块 react-app-rewired, babel-plugin-import, react-app-rewire-less,根目录添加config-overrides.js,修改npm script即可, 一切正常

webpack 构建多页面应用初探

如何使用webpack构建多页面应用,这是一个我一直在想和解决的问题。网上也给出了很多的例子,很多想法。猛一看,觉得有那么点儿意思,但仔细看也就那样。使用webpack这个构建工具,可以使我们少考虑很多的问题。

通用、封装、简化 webpack 配置

现在,基本上前端的项目打包都会用上 webpack,因为 webpack 提供了无与伦比强大的功能和生态。但在创建一个项目的时候,总是免不了要配置 webpack,很是麻烦。简化 webpack 配置的一种方式是使用社区封装好的库,比如 roadhog。

webpack 前后端分离开发接口调试解决方案,proxyTable解决方案

如果你有单独的后端开发服务器 API,并且希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用。dev-server 使用了非常强大的 http-proxy-middleware 包。更多高级用法,请查阅其文档。在 localhost:3000 上有后端服务的话,你可以这样启用代理

浅谈webpack优化

由于前端的快速发展,相关工具的发展速度也是相当迅猛,各大框架例如vue,react都有自己优秀的脚手架工具来帮助我们快速启动一个新项目,也正式因为这个原因,我们对于脚手架中最关键的一环webpack相关的优化知之甚少

Vue中使用webpack别名的方法

Vue中使用webpack别名的方法,需要引入公共文件,但是公共文件的文件路径里当前文件很远,那么就会形成上面示例中的那种路径很长的情况。而因为文件目录是约定俗成的,不可轻易更改,无法修改相对路径。那么该怎么办呢?

webpack项目轻松混用css module

本文讲述css-loader开启css模块功能之后,如何与引用的npm包中样式文件不产生冲突。比如antd-mobilenpm包的引入。在不做特殊处理的前提下,样式文件将会被转译成css module。

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯