Skip to content

Commit 2af0561

Browse files
committed
Safely concatenate JavaScript
Most JavaScript packages wrap their code in IIFEs, which is good. Pipeline used to concatenate JS only with a newline. That can break those IIFES though: (function() { // package A }()) // No semicolon! Most people put one here, but unfortunately not all! (function() { // package B }()); The above is equivalent to: (function() { // package A }())(function() { // package B }()); Suddenly we have a function call! With this commit, JS is concatenated with a newline followed by a semicolon, which fixes the above issue: (function() { // package A }()) // No semicolon! Most people put one here, but unfortunately not all! ;(function() { // package B }()); There is no need to worry about superfluos semicolons, such as: (function() { // package A }()); ;;(function() { // package B }()); That is still valid JavaScript and the extra semicolons will be removed by the minifier.
1 parent a12407e commit 2af0561

File tree

4 files changed

+16
-8
lines changed

4 files changed

+16
-8
lines changed

pipeline/compressors/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@ def reconstruct(match):
142142

143143
def concatenate(self, paths):
144144
"""Concatenate together a list of files"""
145-
return "\n".join([self.read_text(path) for path in paths])
145+
# Note how a semicolon is added between the two files to make sure that
146+
# their behavior is not changed. '(expression1)\n(expression2)' calls
147+
# `expression1` with `expression2` as an argument! Superfluos semicolons
148+
# are valid in JavaScript and will be removed by the minifier.
149+
return "\n;".join([self.read_text(path) for path in paths])
146150

147151
def construct_asset_path(self, asset_path, css_path, output_filename, variant=None):
148152
"""Return a rewritten asset URL for a stylesheet"""

tests/assets/js/first.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
function concat() {
2-
console.log(arguments);
3-
}
1+
(function() {
2+
window.concat = function() {
3+
console.log(arguments);
4+
}
5+
}()) // No semicolon

tests/assets/js/second.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
function cat() {
2-
console.log("hello world");
3-
}
1+
(function() {
2+
window.cat = function() {
3+
console.log("hello world");
4+
}
5+
}());

tests/tests/test_compressor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_concatenate(self):
4646
_('pipeline/js/first.js'),
4747
_('pipeline/js/second.js')
4848
])
49-
self.assertEqual("""function concat() {\n console.log(arguments);\n}\n\nfunction cat() {\n console.log("hello world");\n}\n""", js)
49+
self.assertEqual("""(function() {\n window.concat = function() {\n console.log(arguments);\n }\n}()) // No semicolon\n\n;(function() {\n window.cat = function() {\n console.log("hello world");\n }\n}());\n""", js)
5050

5151
@patch.object(base64, 'b64encode')
5252
def test_encoded_content(self, mock):

0 commit comments

Comments
 (0)