From b85e01d0a8e5f0369a6d8cf96274adadbaa2c7d0 Mon Sep 17 00:00:00 2001 From: Nejc Habjan Date: Sat, 16 Oct 2021 14:48:56 +0200 Subject: [PATCH] Support minifying CSS files --- .github/workflows/test.yml | 25 ++++++ README.md | 13 ++- mkdocs_minify_plugin/plugin.py | 83 +++++++++++++------ requirements.txt | 3 +- setup.py | 9 +- .../fixtures/docs/extra_assets/css/style.css | 4 + tests/fixtures/docs/extra_assets/js/script.js | 2 + tests/fixtures/docs/index.md | 17 ++++ tests/fixtures/mkdocs.yml | 14 ++++ tests/requirements.txt | 1 + tests/test_basic.py | 33 ++++++++ 11 files changed, 173 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 tests/fixtures/docs/extra_assets/css/style.css create mode 100644 tests/fixtures/docs/extra_assets/js/script.js create mode 100644 tests/fixtures/docs/index.md create mode 100644 tests/fixtures/mkdocs.yml create mode 100644 tests/requirements.txt create mode 100644 tests/test_basic.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5e6c4be --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install dependencies + run: | + pip install -r tests/requirements.txt + pip install -e . + - name: Run tests + run: pytest -v diff --git a/README.md b/README.md index 0a77529..6d6c732 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # mkdocs-minify-plugin -An MkDocs plugin to minify HTML and/or JS files prior to being written to disk. +An MkDocs plugin to minify HTML, JS or CSS files prior to being written to disk. HTML minification is done using [htmlmin](https://github.com/mankyd/htmlmin). JS minification is done using [jsmin](https://github.com/tikitu/jsmin/). +CSS minification is done using [csscompressor](https://github.com/sprymix/csscompressor). + ## Setup Install the plugin using pip: @@ -19,11 +21,15 @@ plugins: - minify: minify_html: true minify_js: true + minify_css: true htmlmin_opts: remove_comments: true js_files: - my/javascript/dir/file1.js - my/javascript/dir/file2.js + css_files: + - my/css/dir/file1.css + - my/css/dir/file2.css ``` > **Note:** If you have no `plugins` entry in your config file yet, you'll likely also want to add the `search` plugin. MkDocs enables it by default if there is no `plugins` entry set, but now you have to enable it explicitly. @@ -33,9 +39,12 @@ plugins: - `minify_html`: Sets whether HTML files should be minified. Defaults to `false`. - `htmlmin_opts`: Sets runtime htmlmin API options using the [config parameters of htmlmin](https://htmlmin.readthedocs.io/en/latest/reference.html#main-functions) - `minify_js`: Sets whether JS files should be minified. Defaults to `false`. If set to `true`, you must specificy the JS to be minified files using `js_files` (see below). +- `minify_css`: Sets whether CSS files should be minified. Defaults to `false`. If set to `true`, you must specificy the CSS to be minified files using `css_files` (see below). - `js_files`: List of JS files to be minified. The plugin will generate minified versions of these files and save them as `.min.js` in the output directory. +- `css_files`: List of CSS files to be minified. The plugin will generate minified versions of these files and save them as `.min.css` in the output directory. -> **Note:** When using `minify_js`, you don't have to modify the `extra_javascript` entries in your `mkdocs.yml` file. The plugins automatically takes care of that. +> **Note:** When using `minify_jss` or `minify_css`, you don't have to modify the `extra_javascript` or `extra_css` entries +in your `mkdocs.yml` file. The plugins automatically takes care of that. More information about plugins in the [MkDocs documentation][mkdocs-plugins]. diff --git a/mkdocs_minify_plugin/plugin.py b/mkdocs_minify_plugin/plugin.py index 7cae99f..9411390 100644 --- a/mkdocs_minify_plugin/plugin.py +++ b/mkdocs_minify_plugin/plugin.py @@ -9,18 +9,67 @@ from mkdocs.plugins import BasePlugin import mkdocs.structure.files -from jsmin import jsmin +import csscompressor +import jsmin from htmlmin import minify + +EXTRAS = { + "js": "extra_javascript", + "css": "extra_css", +} +MINIFIERS = { + "js": jsmin.jsmin, + "css": csscompressor.compress +} + + +def _minified_asset(file_name, file_type): + return file_name.replace('.' + file_type, '.min.' + file_type) + + class MinifyPlugin(BasePlugin): config_scheme = ( ('minify_html', mkdocs.config.config_options.Type(bool, default=False)), - ('htmlmin_opts', mkdocs.config.config_options.Type((str, dict), default=None)), ('minify_js', mkdocs.config.config_options.Type(bool, default=False)), - ('js_files', mkdocs.config.config_options.Type((str, list), default=None)) + ('minify_css', mkdocs.config.config_options.Type(bool, default=False)), + ('js_files', mkdocs.config.config_options.Type((str, list), default=None)), + ('css_files', mkdocs.config.config_options.Type((str, list), default=None)), + ('htmlmin_opts', mkdocs.config.config_options.Type((str, dict), default=None)), ) + def _minify(self, file_type, config): + minify_func = MINIFIERS[file_type] + files = self.config[file_type + '_files'] or [] + + if not isinstance(files, list): + files = [files] + for file in files: + # Read file and minify + fn = config['site_dir'] + '/' + file + if os.sep != '/': + fn = fn.replace(os.sep, '/') + with open(fn, mode="r+", encoding="utf-8") as f: + minified = minify_func(f.read()) + f.seek(0) + f.write(minified) + f.truncate() + # Rename to .min.{file_type} + os.rename(fn, _minified_asset(fn, file_type)) + + def _minify_extra_config(self, file_type, config): + """Change extra_ entries so they point to the minified files.""" + files = self.config[file_type + '_files'] or [] + extra = EXTRAS[file_type] + + if not isinstance(files, list): + files = [files] + for file in files: + if file in config[extra]: + config[extra][config[extra].index(file)] = _minified_asset(file, file_type) + return config + def on_post_page(self, output_content, page, config): if self.config['minify_html']: opts = self.config['htmlmin_opts'] or {} @@ -40,30 +89,14 @@ def on_post_template(self, output_content, template_name, config): def on_pre_build(self, config): if self.config['minify_js']: - jsfiles = self.config['js_files'] or [] - if not isinstance(jsfiles, list): - jsfiles = [jsfiles] - for jsfile in jsfiles: - # Change extra_javascript entries so they point to the minified files - if jsfile in config['extra_javascript']: - config['extra_javascript'][config['extra_javascript'].index(jsfile)] = jsfile.replace('.js', '.min.js') + config = self._minify_extra_config('js', config) + if self.config['minify_css']: + config = self._minify_extra_config('css', config) return config def on_post_build(self, config): if self.config['minify_js']: - jsfiles = self.config['js_files'] or [] - if not isinstance(jsfiles, list): - jsfiles = [jsfiles] - for jsfile in jsfiles: - # Read JS file and minify - fn = config['site_dir'] + '/' + jsfile - if os.sep != '/': - fn = fn.replace(os.sep, '/') - with open(fn, mode="r+", encoding="utf-8") as f: - minified = jsmin(f.read()) - f.seek(0) - f.write(minified) - f.truncate() - # Rename to .min.js - os.rename(fn, fn.replace('.js','.min.js')) + self._minify('js', config) + if self.config['minify_css']: + self._minify('css', config) return config diff --git a/requirements.txt b/requirements.txt index 224acc9..a8038da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ mkdocs>=1.0.4 htmlmin>=0.1.4 -jsmin>=3.0.0 \ No newline at end of file +jsmin>=3.0.0 +csscompressor>=0.9.5 diff --git a/setup.py b/setup.py index 3c9b917..a9c40ce 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,8 @@ setup( name='mkdocs-minify-plugin', - version='0.4.1', - description='An MkDocs plugin to minify HTML and/or JS files prior to being written to disk', + version='0.5.0', + description='An MkDocs plugin to minify HTML, JS or CSS files prior to being written to disk', long_description='', keywords='mkdocs minify publishing documentation html css', url='https://github.com/byrnereese/mkdocs-minify-plugin', @@ -12,7 +12,10 @@ license='MIT', python_requires='>=3.0', install_requires=[ - 'mkdocs>=1.0.4','htmlmin>=0.1.4', 'jsmin>=3.0.0' + 'mkdocs>=1.0.4', + 'htmlmin>=0.1.4', + 'jsmin>=3.0.0', + 'csscompressor>=0.9.5', ], classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tests/fixtures/docs/extra_assets/css/style.css b/tests/fixtures/docs/extra_assets/css/style.css new file mode 100644 index 0000000..b0d7542 --- /dev/null +++ b/tests/fixtures/docs/extra_assets/css/style.css @@ -0,0 +1,4 @@ +/* extra css */ +.ui-hidden { + display: none; +} diff --git a/tests/fixtures/docs/extra_assets/js/script.js b/tests/fixtures/docs/extra_assets/js/script.js new file mode 100644 index 0000000..1e1eb38 --- /dev/null +++ b/tests/fixtures/docs/extra_assets/js/script.js @@ -0,0 +1,2 @@ +// extra js +console.log('Hello World'); diff --git a/tests/fixtures/docs/index.md b/tests/fixtures/docs/index.md new file mode 100644 index 0000000..000ea34 --- /dev/null +++ b/tests/fixtures/docs/index.md @@ -0,0 +1,17 @@ +# Welcome to MkDocs + +For full documentation visit [mkdocs.org](https://www.mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs -h` - Print help message and exit. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. diff --git a/tests/fixtures/mkdocs.yml b/tests/fixtures/mkdocs.yml new file mode 100644 index 0000000..248b3a9 --- /dev/null +++ b/tests/fixtures/mkdocs.yml @@ -0,0 +1,14 @@ +site_name: My Docs + +plugins: + - search + - minify: + minify_html: true + minify_js: true + minify_css: true + htmlmin_opts: + remove_comments: true + js_files: + - extra_assets/js/script.js + css_files: + - extra_assets/css/style.css diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..eff69d9 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1 @@ +pytest>=6.2.5,<7.0.0 diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..964365a --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,33 @@ +from distutils.dir_util import copy_tree +from pathlib import Path +from subprocess import check_call + +import pytest + + +@pytest.fixture +def extra_dir(tmp_path): + return tmp_path / "site" / "extra_assets" + + +@pytest.fixture +def mkdocs_build(tmp_path): + fixture_dir = str(Path(__file__).parent / "fixtures") + temp_dir = str(tmp_path) + + copy_tree(fixture_dir, temp_dir) + return check_call(["mkdocs", "build", "-f", f"{temp_dir}/mkdocs.yml"]) + + +def test_css_is_minified(mkdocs_build, extra_dir): + with open(extra_dir / "css" / "style.min.css", "r") as f: + minified = f.read() + + assert minified == r".ui-hidden{display:none}" + + +def test_js_is_minifed(mkdocs_build, extra_dir): + with open(extra_dir / "js" / "script.min.js", "r") as f: + minifed = f.read() + + assert minifed == r"console.log('Hello World');"