Skip to content

Commit 238bba2

Browse files
committed
Added support for Subresource Integrity
1 parent 0c07ec6 commit 238bba2

File tree

6 files changed

+110
-5
lines changed

6 files changed

+110
-5
lines changed

tests/app/templates/single.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{% load render_bundle webpack_static from webpack_loader %}
2+
<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<meta charset="UTF-8">
6+
<title>Example</title>
7+
{% render_bundle 'main' 'css' %}
8+
</head>
9+
10+
<body>
11+
<div id="react-app"></div>
12+
{% render_bundle 'main' 'js' %}
13+
</body>
14+
</html>

tests/app/tests/test_webpack.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,25 @@ def test_preload(self):
210210
'<script src="/static/django_webpack_loader_bundles/main.js" >'
211211
'</script>'), result.rendered_content)
212212

213+
def test_integrity(self):
214+
self.compile_bundles('webpack.config.integrity.js')
215+
216+
loader = get_loader(DEFAULT_CONFIG)
217+
with patch.dict(loader.config, {'INTEGRITY': True}):
218+
view = TemplateView.as_view(template_name='single.html')
219+
request = self.factory.get('/')
220+
result = view(request)
221+
222+
self.assertIn((
223+
'<script src="/static/django_webpack_loader_bundles/main.js" '
224+
'integrity="sha256-1wgFMxcDlOWYV727qRvWNoPHdnOGFNVMLuKd25cjR+o=">'
225+
'</script>'), result.rendered_content)
226+
self.assertIn((
227+
'<link href="/static/django_webpack_loader_bundles/main.css" rel="stylesheet" '
228+
'integrity="sha256-cYWwRvS04/VsttQYx4BalKYrBDuw5t8vKFhWB/LKX30="/>'),
229+
result.rendered_content
230+
)
231+
213232
def test_append_extensions(self):
214233
self.compile_bundles('webpack.config.gzipTest.js')
215234
view = TemplateView.as_view(template_name='append_extensions.html')

tests/webpack.config.integrity.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
var path = require("path");
2+
var webpack = require('webpack');
3+
var BundleTracker = require('webpack-bundle-tracker');
4+
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
5+
6+
7+
module.exports = {
8+
context: __dirname,
9+
entry: './assets/js/index',
10+
output: {
11+
path: path.resolve('./assets/django_webpack_loader_bundles/'),
12+
filename: "[name].js"
13+
},
14+
15+
plugins: [
16+
new MiniCssExtractPlugin(),
17+
new BundleTracker({path: __dirname, filename: './webpack-stats.json', integrity: true}),
18+
],
19+
20+
module: {
21+
rules: [
22+
// we pass the output from babel loader to react-hot loader
23+
{
24+
test: /\.jsx?$/,
25+
exclude: /node_modules/,
26+
use: {
27+
loader: 'babel-loader',
28+
options: {
29+
presets: ['@babel/preset-react']
30+
}
31+
}
32+
},
33+
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], }
34+
],
35+
},
36+
37+
resolve: {
38+
modules: ['node_modules'],
39+
extensions: ['.js', '.jsx']
40+
},
41+
}

webpack_loader/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
'TIMEOUT': None,
1616
'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
1717
'LOADER_CLASS': 'webpack_loader.loader.WebpackLoader',
18+
'INTEGRITY': False,
1819
}
1920
}
2021

webpack_loader/loader.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ def get_assets(self):
3838
return self._assets[self.name]
3939
return self.load_assets()
4040

41+
def get_integrity_attr(self, chunk):
42+
if not self.config['INTEGRITY']:
43+
return ''
44+
45+
integrity = chunk.get('integrity')
46+
if not integrity:
47+
raise WebpackLoaderBadStatsError(
48+
"The stats file does not contain valid data: INTEGRITY is set to True, "
49+
"but chunk does not contain \"integrity\" key.")
50+
51+
return 'integrity="{}"'.format(integrity.partition(' ')[0])
52+
4153
def filter_chunks(self, chunks):
4254
filtered_chunks = []
4355

@@ -53,9 +65,15 @@ def map_chunk_files_to_url(self, chunks):
5365
assets = self.get_assets()
5466
files = assets['assets']
5567

68+
add_integrity = self.config['INTEGRITY']
69+
5670
for chunk in chunks:
5771
url = self.get_chunk_url(files[chunk])
58-
yield { 'name': chunk, 'url': url }
72+
73+
if add_integrity:
74+
yield {'name': chunk, 'url': url, 'integrity': files[chunk].get('integrity')}
75+
else:
76+
yield {'name': chunk, 'url': url}
5977

6078
def get_chunk_url(self, chunk_file):
6179
public_path = chunk_file.get('publicPath')

webpack_loader/utils.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def get_as_tags(bundle_name, extension=None, config='DEFAULT', suffix='', attrs=
6060

6161
bundle = _get_bundle(bundle_name, extension, config)
6262
tags = []
63+
64+
loader = get_loader(config)
65+
6366
for chunk in bundle:
6467
if chunk['name'].endswith(('.js', '.js.gz')):
6568
if is_preload:
@@ -68,12 +71,21 @@ def get_as_tags(bundle_name, extension=None, config='DEFAULT', suffix='', attrs=
6871
).format(''.join([chunk['url'], suffix]), attrs))
6972
else:
7073
tags.append((
71-
'<script src="{0}" {1}></script>'
72-
).format(''.join([chunk['url'], suffix]), attrs))
74+
'<script src="{0}" {1}{2}></script>'
75+
).format(
76+
''.join([chunk['url'], suffix]),
77+
attrs,
78+
loader.get_integrity_attr(chunk),
79+
))
7380
elif chunk['name'].endswith(('.css', '.css.gz')):
7481
tags.append((
75-
'<link href="{0}" rel={2} {1}/>'
76-
).format(''.join([chunk['url'], suffix]), attrs, '"stylesheet"' if not is_preload else '"preload" as="style"'))
82+
'<link href="{0}" rel={2} {1}{3}/>'
83+
).format(
84+
''.join([chunk['url'], suffix]),
85+
attrs,
86+
'"stylesheet"' if not is_preload else '"preload" as="style"',
87+
loader.get_integrity_attr(chunk),
88+
))
7789
return tags
7890

7991

0 commit comments

Comments
 (0)