diff --git a/.travis.yml b/.travis.yml
index ff48f32..95bd517 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,10 +3,9 @@ python:
- 2.7
- 3.4
before_install:
- - nvm install 4.0
- - npm install -g node-sass postcss-cli autoprefixer
- - npm install -g browserify babelify babel-preset-es2015
+ - nvm install 6.0
+ - npm install
install:
- pip install -e .[test]
script:
- - py.test
+ - py.test tests/unit_tests tests/integration_tests
diff --git a/README.md b/README.md
index 7e31a68..a0f494a 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,12 @@ $title-size: 30px;
You need `node-sass`, `postcss-cli` and `autoprefixer` to be installed. Quick install:
+```sh
+npm install node-sass postcss-cli autoprefixer
+```
+
+Or you can install them globally (you need to set `COMPRESS_LOCAL_NPM_INSTALL = False`):
+
```sh
npm install -g node-sass postcss-cli autoprefixer
```
@@ -136,17 +142,47 @@ export default class {
You need `browserify`, `babelify` and `babel-preset-es2015` to be installed. Quick install:
+```sh
+npm install browserify babelify babel-preset-es2015
+```
+
+Or you can install them globally (you need to set `COMPRESS_LOCAL_NPM_INSTALL = False`):
+
```sh
npm install -g browserify babelify babel-preset-es2015
```
## Django settings
+### `COMPRESS_LOCAL_NPM_INSTALL`
+
+Whether you install required NPM packages _locally_.
+
+Default: `True`.
+
### `COMPRESS_NODE_MODULES`
-Path to `node_modules` where `browserify`, `babelify`, `autoprefixer`, etc, are installed.
+Path to `node_modules` where `babelify`, `autoprefixer`, etc, libs are installed.
+
+Default: `./node_modules` if `COMPRESS_LOCAL_NPM_INSTALL` else `/usr/lib/node_modules`.
+
+### `COMPRESS_NODE_SASS_BIN`
+
+`node-sass` executable. It may be just the executable name (if it's on `PATH`) or the executable path.
+
+Default: `./node_modules/.bin/node-sass` if `COMPRESS_LOCAL_NPM_INSTALL` else `node-sass`.
-Default: `/usr/lib/node_modules`
+### `COMPRESS_POSTCSS_BIN`
+
+`postcss` executable. It may be just the executable name (if it's on `PATH`) or the executable path.
+
+Default: `./node_modules/.bin/postcss` if `COMPRESS_LOCAL_NPM_INSTALL` else `postcss`.
+
+### `COMPRESS_AUTOPREFIXER_BROWSERS`
+
+Browser versions config for Autoprefixer.
+
+Default: `ie >= 9, > 5%`.
### `COMPRESS_SCSS_COMPILER_CMD`
@@ -154,24 +190,26 @@ Command that will be executed to transform SCSS into CSS code.
Default:
-```sh
-node-sass --output-style expanded {paths} "{infile}" "{outfile}" &&
-postcss --use "{node_modules}/autoprefixer" --autoprefixer.browsers "{autoprefixer_browsers}" -r "{outfile}"
+```py
+'{node_sass_bin} --output-style expanded {paths} "{infile}" "{outfile}" && '
+'{postcss_bin} --use "{node_modules}/autoprefixer" --autoprefixer.browsers "{autoprefixer_browsers}" -r "{outfile}"'
```
-Placeholders:
+Placeholders (i.e. they **can be re-used** in custom `COMPRESS_SCSS_COMPILER_CMD` string):
+- `{node_sass_bin}` - value from `COMPRESS_NODE_SASS_BIN`
+- `{postcss_bin}` - value from `COMPRESS_POSTCSS_BIN`
- `{infile}` - input file path
- `{outfile}` - output file path
- `{paths}` - specially for `node-sass`, include all Django app static folders:
`--include-path=/path/to/app-1/static/ --include-path=/path/to/app-2/static/ ...`
- `{node_modules}` - see `COMPRESS_NODE_MODULES` setting
-- `{autoprefixer_browsers}` - see `COMPRESS_AUTOPREFIXER_BROWSERS` setting
+- `{autoprefixer_browsers}` - value from `COMPRESS_AUTOPREFIXER_BROWSERS`
-### `COMPRESS_AUTOPREFIXER_BROWSERS`
+### `COMPRESS_BROWSERIFY_BIN`
-Browser versions config for Autoprefixer.
+`browserify` executable. It may be just the executable name (if it's on `PATH`) or the executable path.
-Default: `ie >= 9, > 5%`
+Default: `./node_modules/.bin/browserify` if `COMPRESS_LOCAL_NPM_INSTALL` else `browserify`.
### `COMPRESS_ES6_COMPILER_CMD`
@@ -179,12 +217,14 @@ Command that will be executed to transform ES6 into ES5 code.
Default:
-```sh
-export NODE_PATH="{paths}" && browserify "{infile}" -o "{outfile}" --no-bundle-external --node
--t [ "{node_modules}/babelify" --presets="{node_modules}/babel-preset-es2015" ]
+```py
+'export NODE_PATH="{paths}" && '
+'{browserify_bin} "{infile}" -o "{outfile}" --no-bundle-external --node '
+'-t [ "{node_modules}/babelify" --presets="{node_modules}/babel-preset-es2015" ]'
```
Placeholders:
+- `{browserify_bin}` - value from `COMPRESS_BROWSERIFY_BIN`
- `{infile}` - input file path
- `{outfile}` - output file path
- `{paths}` - specially for `browserify`, include all Django app static folders:
@@ -197,5 +237,6 @@ Placeholders:
git clone https://github.com/kottenator/django-compressor-toolkit.git
cd django-compressor-toolkit
pip install -e '.[test]'
+npm install
py.test
```
diff --git a/compressor_toolkit/__init__.py b/compressor_toolkit/__init__.py
index 78a5b0d..081e6de 100644
--- a/compressor_toolkit/__init__.py
+++ b/compressor_toolkit/__init__.py
@@ -1,5 +1,5 @@
# PEP 440 - version number format
-VERSION = (0, 5, 1, 'dev0')
+VERSION = (0, 6, 0, 'dev0')
# PEP 396 - module version variable
__version__ = '.'.join(map(str, VERSION))
diff --git a/compressor_toolkit/apps.py b/compressor_toolkit/apps.py
index 772d4e8..c09e7b5 100644
--- a/compressor_toolkit/apps.py
+++ b/compressor_toolkit/apps.py
@@ -1,3 +1,5 @@
+import os
+
from django.apps.config import AppConfig
from django.conf import settings
@@ -5,24 +7,49 @@
class CompressorToolkitConfig(AppConfig):
name = 'compressor_toolkit'
+ LOCAL_NPM_INSTALL = getattr(settings, 'COMPRESS_LOCAL_NPM_INSTALL', True)
+
# Path to 'node_modules' where browserify, babelify, autoprefixer, etc, are installed
- NODE_MODULES = getattr(settings, 'COMPRESS_NODE_MODULES', None) or '/usr/lib/node_modules'
+ NODE_MODULES = getattr(
+ settings,
+ 'COMPRESS_NODE_MODULES',
+ os.path.abspath('node_modules') if LOCAL_NPM_INSTALL else '/usr/lib/node_modules'
+ )
- # Custom SCSS transpiler command
- SCSS_COMPILER_CMD = getattr(settings, 'COMPRESS_SCSS_COMPILER_CMD', None) or (
- 'node-sass --output-style expanded {paths} "{infile}" "{outfile}" && '
- 'postcss --use "{node_modules}/autoprefixer" '
- '--autoprefixer.browsers "{autoprefixer_browsers}" -r "{outfile}"'
+ # node-sass executable
+ NODE_SASS_BIN = getattr(
+ settings,
+ 'COMPRESS_NODE_SASS_BIN',
+ 'node_modules/.bin/node-sass' if LOCAL_NPM_INSTALL else 'node-sass'
+ )
+
+ # postcss executable
+ POSTCSS_BIN = getattr(
+ settings,
+ 'COMPRESS_POSTCSS_BIN',
+ 'node_modules/.bin/node-sass' if LOCAL_NPM_INSTALL else 'postcss'
)
# Browser versions config for Autoprefixer
- AUTOPREFIXER_BROWSERS = getattr(settings, 'COMPRESS_AUTOPREFIXER_BROWSERS', None) or (
- 'ie >= 9, > 5%'
+ AUTOPREFIXER_BROWSERS = getattr(settings, 'COMPRESS_AUTOPREFIXER_BROWSERS', 'ie >= 9, > 5%')
+
+ # Custom SCSS transpiler command
+ SCSS_COMPILER_CMD = getattr(settings, 'COMPRESS_SCSS_COMPILER_CMD', (
+ '{node_sass_bin} --output-style expanded {paths} "{infile}" > "{outfile}" && '
+ '{postcss_bin} --use "{node_modules}/autoprefixer" '
+ '--autoprefixer.browsers "{autoprefixer_browsers}" -r "{outfile}"'
+ ))
+
+ # browserify executable
+ BROWSERIFY_BIN = getattr(
+ settings,
+ 'COMPRESS_BROWSERIFY_BIN',
+ 'node_modules/.bin/browserify' if LOCAL_NPM_INSTALL else 'browserify'
)
# Custom ES6 transpiler command
- ES6_COMPILER_CMD = getattr(settings, 'COMPRESS_ES6_COMPILER_CMD', None) or (
+ ES6_COMPILER_CMD = getattr(settings, 'COMPRESS_ES6_COMPILER_CMD', (
'export NODE_PATH="{paths}" && '
- 'browserify "{infile}" -o "{outfile}" --no-bundle-external --node '
+ '{browserify_bin} "{infile}" -o "{outfile}" '
'-t [ "{node_modules}/babelify" --presets="{node_modules}/babel-preset-es2015" ]'
- )
+ ))
diff --git a/compressor_toolkit/precompilers.py b/compressor_toolkit/precompilers.py
index daabbf2..0527fca 100644
--- a/compressor_toolkit/precompilers.py
+++ b/compressor_toolkit/precompilers.py
@@ -77,6 +77,8 @@ class SCSSCompiler(BaseCompiler):
"""
command = app_config.SCSS_COMPILER_CMD
options = (
+ ('node_sass_bin', app_config.NODE_SASS_BIN),
+ ('postcss_bin', app_config.POSTCSS_BIN),
('paths', ' '.join(['--include-path {}'.format(s) for s in get_all_static()])),
('node_modules', app_config.NODE_MODULES),
('autoprefixer_browsers', app_config.AUTOPREFIXER_BROWSERS),
@@ -103,6 +105,7 @@ class ES6Compiler(BaseCompiler):
"""
command = app_config.ES6_COMPILER_CMD
options = (
+ ('browserify_bin', app_config.BROWSERIFY_BIN),
('paths', os.pathsep.join(get_all_static())),
('node_modules', app_config.NODE_MODULES)
)
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..bbc8320
--- /dev/null
+++ b/package.json
@@ -0,0 +1,11 @@
+{
+ "private": true,
+ "devDependencies": {
+ "node-sass": "*",
+ "postcss-cli": "*",
+ "autoprefixer": "*",
+ "browserify": "*",
+ "babelify": "*",
+ "babel-preset-es2015": "*"
+ }
+}
diff --git a/setup.cfg b/setup.cfg
index cb1fddf..522fe2d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,9 +1,9 @@
-[pytest]
+[tool:pytest]
python_paths = tests/test_project
testpaths = tests/unit_tests tests/integration_tests
addopts =
--ds test_project.settings
--cov compressor_toolkit
- --cov-report term
+ --cov-report term-missing
--cov-report html
--cov-report xml
diff --git a/setup.py b/setup.py
index ed22789..c624e8b 100644
--- a/setup.py
+++ b/setup.py
@@ -18,19 +18,16 @@
license='MIT',
packages=find_packages(exclude=['tests', 'tests.*']),
include_package_data=True,
- setup_requires=[
- 'setuptools-git'
- ],
install_requires=[
- 'django-compressor~=1.5'
+ 'django-compressor>=1.5'
],
extras_require={
'test': [
- 'django',
- 'pytest',
- 'pytest-django',
- 'pytest-cov',
- 'pytest-pythonpath'
+ 'django~=1.8',
+ 'pytest~=3.0',
+ 'pytest-django~=3.0',
+ 'pytest-cov~=2.4',
+ 'pytest-pythonpath~=0.7'
]
},
classifiers=[
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/integration_tests/__init__.py b/tests/integration_tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/integration_tests/test_views.py b/tests/integration_tests/test_views.py
index f4ca8f1..6a868f4 100644
--- a/tests/integration_tests/test_views.py
+++ b/tests/integration_tests/test_views.py
@@ -1,3 +1,5 @@
+import re
+
from django.core.urlresolvers import reverse
@@ -10,8 +12,8 @@ def test_view_with_scss_file(client, precompiled):
"""
response = client.get(reverse('scss-file'))
assert response.status_code == 200
- assert precompiled('app/layout.scss', 'css') == \
- '.title {\n font: bold 30px Arial, sans-serif;\n}\n'
+ assert precompiled('app/layout.scss', 'css').strip() == \
+ '.title {\n font: bold 30px Arial, sans-serif;\n}'
def test_view_with_inline_scss(client):
@@ -22,9 +24,10 @@ def test_view_with_inline_scss(client):
"""
response = client.get(reverse('scss-inline'))
assert response.status_code == 200
- assert b'' in response.content
+ assert re.search(
+ r'',
+ response.content.decode('utf8')
+ )
def test_view_with_es6_file(client, precompiled):
@@ -56,7 +59,28 @@ def test_view_with_es6_file(client, precompiled):
'new _framework2.default();\n'
'new _framework2.default(\'1.0.1\');\n'
'\n'
- '},{"base/framework":undefined}]},{},[1]);\n'
+ '},{"base/framework":2}],2:[function(require,module,exports){\n'
+ '\'use strict\';\n'
+ '\n'
+ 'Object.defineProperty(exports, "__esModule", {\n'
+ ' value: true\n'
+ '});\n'
+ '\n'
+ 'function _classCallCheck(instance, Constructor) {'
+ ' if (!(instance instanceof Constructor)) {'
+ ' throw new TypeError("Cannot call a class as a function"); } }\n'
+ '\n'
+ 'var version = exports.version = \'1.0\';\n'
+ '\n'
+ 'var _class = function _class(customVersion) {\n'
+ ' _classCallCheck(this, _class);\n'
+ '\n'
+ ' console.log(\'Framework v\' + (customVersion || version) + \' initialized\');\n'
+ '};\n'
+ '\n'
+ 'exports.default = _class;\n'
+ '\n'
+ '},{}]},{},[1]);\n'
)
diff --git a/tests/test_project/__init__.py b/tests/test_project/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/test_project/test_project/settings.py b/tests/test_project/test_project/settings.py
index 983b8df..c6818d1 100644
--- a/tests/test_project/test_project/settings.py
+++ b/tests/test_project/test_project/settings.py
@@ -18,10 +18,25 @@
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder'
)
-MIDDLEWARE_CLASSES = ()
-TEMPLATE_CONTEXT_PROCESSORS = (
- 'django.template.context_processors.static',
-)
+# Django < 1.8
+TEMPLATE_CONTEXT_PROCESSORS = [
+ 'django.template.context_processors.static'
+]
+# Django >= 1.8
+TEMPLATES = [{
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.static'
+ ]
+ }
+}]
+# Django < 1.10
+MIDDLEWARE_CLASSES = []
+# Django >= 1.10
+MIDDLEWARE = []
+# django-compressor settings
COMPRESS_ROOT = os.path.join(BASE_DIR, 'compressor')
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'compressor_toolkit.precompilers.SCSSCompiler'),
@@ -30,4 +45,5 @@
COMPRESS_ENABLED = False
# django-compressor-toolkit settings; see compressor_toolkit/apps.py for details
-COMPRESS_NODE_MODULES = os.getenv('COMPRESS_NODE_MODULES')
+if 'COMPRESS_NODE_MODULES' in os.environ:
+ COMPRESS_NODE_MODULES = os.getenv('COMPRESS_NODE_MODULES')
diff --git a/tests/unit_tests/__init__.py b/tests/unit_tests/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/unit_tests/test_precompilers.py b/tests/unit_tests/test_precompilers.py
index 896fb0a..072792e 100644
--- a/tests/unit_tests/test_precompilers.py
+++ b/tests/unit_tests/test_precompilers.py
@@ -15,8 +15,8 @@ def test_scss_compiler():
}
}
'''
- output_css = '.a .b {\n padding-left: 5px;\n padding-right: 6px;\n}\n'
- assert SCSSCompiler(input_scss, {}).input() == output_css
+ output_css = '.a .b {\n padding-left: 5px;\n padding-right: 6px;\n}'
+ assert SCSSCompiler(input_scss, {}).input().strip() == output_css
def test_es6_compiler():