在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-plugin或assets-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]Koa是简约的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/posts/2017…)