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():