├── .nvmrc ├── .eslintignore ├── test ├── lib │ ├── integrations │ │ ├── simple │ │ │ ├── b.css │ │ │ ├── a.css │ │ │ ├── c.css │ │ │ ├── expected-v2.css │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ ├── index.js │ │ │ └── webpack.config.js │ │ ├── source-map │ │ │ ├── a.css │ │ │ ├── b.css │ │ │ ├── c.css │ │ │ ├── index.js │ │ │ ├── expected-v2.css │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ └── webpack.config.js │ │ ├── source-map-false │ │ │ ├── b.css │ │ │ ├── a.css │ │ │ ├── c.css │ │ │ ├── expected-v2.css │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ ├── index.js │ │ │ └── webpack.config.js │ │ ├── css-modules │ │ │ ├── index.js │ │ │ ├── expected-v2.css │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ ├── index.css │ │ │ └── webpack.config.js │ │ ├── source-map-hidden │ │ │ ├── index.js │ │ │ ├── expected-v2.css │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ ├── index.css │ │ │ └── webpack.config.js │ │ ├── source-map-inline │ │ │ ├── index.js │ │ │ ├── index.css │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ ├── webpack.config.js │ │ │ └── expected-v2.css │ │ ├── source-map-with-import │ │ │ ├── b.css │ │ │ ├── c.css │ │ │ ├── a.css │ │ │ ├── index.js │ │ │ ├── import.css │ │ │ ├── webpack.config.js │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ └── expected-v2.css │ │ ├── plugin-output-postfix │ │ │ ├── index.js │ │ │ ├── min.expected.css │ │ │ ├── index.css │ │ │ ├── expected-v2.css │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ └── webpack.config.js │ │ └── source-map-filename │ │ │ ├── index.js │ │ │ ├── index.css │ │ │ ├── expected-v2.css │ │ │ ├── expected-v4.css │ │ │ ├── expected-v5.css │ │ │ └── webpack.config.js │ ├── utils.js │ └── integrations.js ├── webpack4 │ ├── package.json │ ├── integrations.spec.js │ └── webpack.config.js ├── webpack5 │ ├── package.json │ ├── integrations.spec.js │ └── webpack.config.js ├── webpack2 │ ├── package.json │ ├── integrations.spec.js │ └── webpack.config.js └── unit │ └── filter.spec.js ├── docs └── img │ ├── logo.jpg │ ├── css-modules.png │ ├── better.svg │ └── better-full.svg ├── .npmignore ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── workflows │ └── test.yml ├── .gitignore ├── .babelrc ├── .gitattributes ├── jest.config.js ├── lib ├── index.js.flow └── index.d.ts ├── .travis.yml ├── .editorconfig ├── .eslintrc.json ├── LICENSE ├── CONTRIBUTING.md ├── package.json ├── README.md ├── CHANGELOG.md └── src └── index.js /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | * 2 | !src 3 | !src/** 4 | -------------------------------------------------------------------------------- /test/lib/integrations/simple/b.css: -------------------------------------------------------------------------------- 1 | .b { color: #fff; } -------------------------------------------------------------------------------- /test/lib/integrations/simple/a.css: -------------------------------------------------------------------------------- 1 | .a { color: white; } -------------------------------------------------------------------------------- /test/lib/integrations/simple/c.css: -------------------------------------------------------------------------------- 1 | .c { color: #ffffff; } -------------------------------------------------------------------------------- /test/lib/integrations/source-map/a.css: -------------------------------------------------------------------------------- 1 | .a { color: white; } -------------------------------------------------------------------------------- /test/lib/integrations/source-map/b.css: -------------------------------------------------------------------------------- 1 | .b { color: #fff; } -------------------------------------------------------------------------------- /test/lib/integrations/source-map-false/b.css: -------------------------------------------------------------------------------- 1 | .b { color: #fff; } -------------------------------------------------------------------------------- /test/lib/integrations/source-map/c.css: -------------------------------------------------------------------------------- 1 | .c { color: #ffffff; } -------------------------------------------------------------------------------- /test/lib/integrations/css-modules/index.js: -------------------------------------------------------------------------------- 1 | require('./index.css'); -------------------------------------------------------------------------------- /test/lib/integrations/source-map-false/a.css: -------------------------------------------------------------------------------- 1 | .a { color: white; } -------------------------------------------------------------------------------- /test/lib/integrations/source-map-false/c.css: -------------------------------------------------------------------------------- 1 | .c { color: #ffffff; } -------------------------------------------------------------------------------- /test/lib/integrations/source-map-hidden/index.js: -------------------------------------------------------------------------------- 1 | require('./index.css'); -------------------------------------------------------------------------------- /test/lib/integrations/source-map-inline/index.js: -------------------------------------------------------------------------------- 1 | require('./index.css'); -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/b.css: -------------------------------------------------------------------------------- 1 | .b { color: #fff; } -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/c.css: -------------------------------------------------------------------------------- 1 | .c { color: #ffffff; } -------------------------------------------------------------------------------- /test/lib/integrations/plugin-output-postfix/index.js: -------------------------------------------------------------------------------- 1 | require('./index.css'); -------------------------------------------------------------------------------- /test/lib/integrations/source-map-filename/index.js: -------------------------------------------------------------------------------- 1 | require('./index.css'); 2 | -------------------------------------------------------------------------------- /test/lib/integrations/simple/expected-v2.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/simple/expected-v4.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/simple/expected-v5.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-hidden/expected-v2.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-hidden/expected-v4.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-hidden/expected-v5.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-hidden/index.css: -------------------------------------------------------------------------------- 1 | .a:global(.c .d) { color: #fff; } 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-inline/index.css: -------------------------------------------------------------------------------- 1 | .a:global(.c .d) { color: #fff; } 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-filename/index.css: -------------------------------------------------------------------------------- 1 | .a:global(.c .d) { color: #fff; } 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-false/expected-v2.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-false/expected-v4.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-false/expected-v5.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | -------------------------------------------------------------------------------- /docs/img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoobestik/csso-webpack-plugin/HEAD/docs/img/logo.jpg -------------------------------------------------------------------------------- /test/lib/integrations/css-modules/expected-v2.css: -------------------------------------------------------------------------------- 1 | .c .d,.local-a.c .d,.local-c .local-d{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/css-modules/expected-v4.css: -------------------------------------------------------------------------------- 1 | .c .d,.local-a.c .d,.local-c .local-d{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/css-modules/expected-v5.css: -------------------------------------------------------------------------------- 1 | .c .d,.local-a.c .d,.local-c .local-d{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/simple/index.js: -------------------------------------------------------------------------------- 1 | require('./a.css'); 2 | require('./b.css'); 3 | require('./c.css'); -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/a.css: -------------------------------------------------------------------------------- 1 | @import url('import.css'); 2 | .a { color: white; } 3 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map/index.js: -------------------------------------------------------------------------------- 1 | require('./a.css'); 2 | require('./b.css'); 3 | require('./c.css'); -------------------------------------------------------------------------------- /docs/img/css-modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoobestik/csso-webpack-plugin/HEAD/docs/img/css-modules.png -------------------------------------------------------------------------------- /test/lib/integrations/plugin-output-postfix/min.expected.css: -------------------------------------------------------------------------------- 1 | .c .d,.local-a.c .d,.local-c .local-d{color:#fff} 2 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-false/index.js: -------------------------------------------------------------------------------- 1 | require('./a.css'); 2 | require('./b.css'); 3 | require('./c.css'); -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | * 2 | !lib 3 | !lib/** 4 | !CHANGELOG.md 5 | !CONTRIBUTING.md 6 | !LICENSE 7 | !README.md 8 | !yarn.lock 9 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/index.js: -------------------------------------------------------------------------------- 1 | require('./a.css'); 2 | require('./b.css'); 3 | require('./c.css'); -------------------------------------------------------------------------------- /test/lib/integrations/source-map/expected-v2.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | /*# sourceMappingURL=test.css.map*/ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map/expected-v4.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | /*# sourceMappingURL=test.css.map*/ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map/expected-v5.css: -------------------------------------------------------------------------------- 1 | .local-a,.local-b,.local-c{color:#fff} 2 | /*# sourceMappingURL=test.css.map*/ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/css-modules/index.css: -------------------------------------------------------------------------------- 1 | .a:global(.c .d) { color: #fff; } 2 | :global(.c .d) { color: white; } 3 | .c .d { color: #ffffff; } 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Issue Fixed #__ *(if needed)* 2 | 3 | ### Proposed Changes 4 | - 5 | - 6 | - 7 | 8 | **/cc** @zoobestik 9 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-filename/expected-v2.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | /*# sourceMappingURL=prefix-test.css-0-%%unit-hash%%.map*/ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-filename/expected-v4.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | /*# sourceMappingURL=prefix-test.css-0-%%unit-hash%%.map*/ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-filename/expected-v5.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | /*# sourceMappingURL=prefix-test.css-0-%%unit-hash%%.map*/ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/plugin-output-postfix/index.css: -------------------------------------------------------------------------------- 1 | .a:global(.c .d) { color: #fff; } 2 | :global(.c .d) { color: white; } 3 | .c .d { color: #ffffff; } 4 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/import.css: -------------------------------------------------------------------------------- 1 | .import-align { 2 | text-align: center; 3 | } 4 | 5 | .import-color { 6 | color: #fff; 7 | } 8 | -------------------------------------------------------------------------------- /test/lib/integrations/plugin-output-postfix/expected-v2.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d { color: #fff; } 2 | .c .d { color: white; } 3 | .local-c .local-d { color: #ffffff; } 4 | -------------------------------------------------------------------------------- /test/lib/integrations/plugin-output-postfix/expected-v4.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d { color: #fff; } 2 | .c .d { color: white; } 3 | .local-c .local-d { color: #ffffff; } 4 | 5 | -------------------------------------------------------------------------------- /test/lib/integrations/plugin-output-postfix/expected-v5.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d { color: #fff; } 2 | .c .d { color: white; } 3 | .local-c .local-d { color: #ffffff; } 4 | 5 | -------------------------------------------------------------------------------- /test/webpack4/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "css-loader": "^5.2.0", 4 | "mini-css-extract-plugin": "^1.4.0", 5 | "webpack": "^4.46.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/webpack5/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "css-loader": "^5.2.0", 4 | "mini-css-extract-plugin": "^1.4.0", 5 | "webpack": "^5.28.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/webpack2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "css-loader": "^0.28.11", 4 | "extract-text-webpack-plugin": "^2.1.0", 5 | "webpack": "^2.7.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/webpack2/integrations.spec.js: -------------------------------------------------------------------------------- 1 | const runIntegrations = require('../lib/integrations.js'); 2 | 3 | describe('Integrations with webpack 2', function() { 4 | runIntegrations.call(this, __dirname, '2'); 5 | }); 6 | -------------------------------------------------------------------------------- /test/webpack4/integrations.spec.js: -------------------------------------------------------------------------------- 1 | const runIntegrations = require('../lib/integrations.js'); 2 | 3 | describe('Integrations with webpack 4', function() { 4 | runIntegrations.call(this, __dirname, '4'); 5 | }); 6 | -------------------------------------------------------------------------------- /test/webpack5/integrations.spec.js: -------------------------------------------------------------------------------- 1 | const runIntegrations = require('../lib/integrations.js'); 2 | 3 | describe('Integrations with webpack 5', function() { 4 | runIntegrations.call(this, __dirname, '5'); 5 | }); 6 | -------------------------------------------------------------------------------- /test/lib/integrations/simple/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = () => ({ 4 | plugins: [ 5 | new CssoWebpackPlugin(), 6 | ] 7 | }); 8 | -------------------------------------------------------------------------------- /test/lib/integrations/css-modules/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = () => ({ 4 | plugins: [ 5 | new CssoWebpackPlugin(), 6 | ] 7 | }); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # system files 2 | .DS_Store 3 | .idea 4 | *.iml 5 | Thumbs.db 6 | *.swp 7 | 8 | # packages and libs 9 | node_modules 10 | npm-debug.log 11 | package-lock.json 12 | yarn.lock 13 | 14 | .eslintcache 15 | lib/**.js 16 | test/**/_out 17 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-false/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = () => ({ 4 | devtool: false, 5 | plugins: [ 6 | new CssoWebpackPlugin(), 7 | ] 8 | }); 9 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = () => ({ 4 | devtool: 'source-map', 5 | plugins: [ 6 | new CssoWebpackPlugin(), 7 | ] 8 | }); 9 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-hidden/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = () => ({ 4 | devtool: 'hidden-source-map', 5 | plugins: [ 6 | new CssoWebpackPlugin(), 7 | ] 8 | }); 9 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-inline/expected-v4.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | /* {"version":3,"sources":["webpack:///./index.css"],"names":[],"mappings":"AAAA,a,CAAmB,U","file":"test.css","sourcesContent":[".a:global(.c .d) { color: #fff; }\n"],"sourceRoot":""} */ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-inline/expected-v5.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | /* {"version":3,"sources":["webpack:///./index.css"],"names":[],"mappings":"AAAA,a,CAAmB,U","file":"test.css","sourcesContent":[".a:global(.c .d) { color: #fff; }\n"],"sourceRoot":""} */ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-inline/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = () => ({ 4 | devtool: 'inline-source-map', 5 | plugins: [ 6 | new CssoWebpackPlugin(), 7 | ] 8 | }); 9 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = () => ({ 4 | devtool: 'inline-source-map', 5 | plugins: [ 6 | new CssoWebpackPlugin(), 7 | ] 8 | }); 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ "@babel/preset-env", { 4 | "corejs": 3, 5 | "targets": { 6 | "node": "10.0.0" 7 | }, 8 | "loose": true, 9 | "useBuiltIns": "usage" 10 | } ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/lib/integrations/plugin-output-postfix/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = () => ({ 4 | plugins: [ 5 | new CssoWebpackPlugin({ 6 | pluginOutputPostfix: 'min' 7 | }), 8 | ] 9 | }); 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto eol=lf 4 | 5 | # (binary is a macro for -text -diff) 6 | *.png binary 7 | *.jpg binary 8 | *.jpeg binary 9 | *.gif binary 10 | *.woff binary 11 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-inline/expected-v2.css: -------------------------------------------------------------------------------- 1 | .local-a.c .d{color:#fff} 2 | /* {"version":3,"sources":["webpack:///./index.css"],"names":[],"mappings":"AAAA,a,CAAmB,U","file":"test.css","sourcesContent":[".a:global(.c .d) { color: #fff; }\n\n\n\n// WEBPACK FOOTER //\n// ./index.css"],"sourceRoot":""} */ 3 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | testRegex: '(/test/.*\\.spec\\.js)$', 4 | // Keep verbose for CI readability 5 | verbose: true, 6 | // Do not transform; tests run against built lib via npm run build 7 | transform: {}, 8 | // Increase default timeout to accommodate webpack integration runs 9 | testTimeout: 20000, 10 | }; 11 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-filename/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CssoWebpackPlugin = require('../../../../lib/index').default; 2 | 3 | module.exports = ({version}) => ({ 4 | devtool: 'source-map', 5 | output: { 6 | sourceMapFilename: `prefix-[file]-[id]-[${version < 5 ? 'hash' : 'fullhash'}].map[query]` 7 | }, 8 | plugins: [ 9 | new CssoWebpackPlugin(), 10 | ] 11 | }); 12 | -------------------------------------------------------------------------------- /lib/index.js.flow: -------------------------------------------------------------------------------- 1 | declare module 'csso-webpack-plugin' { 2 | declare type PluginOptions = any; 3 | declare type PluginFilterFn = (file: string) => boolean; 4 | declare type PluginFilter = PluginFilterFn | RegExp; 5 | 6 | declare export default class CssoWebpackPlugin { 7 | constructor(options?: PluginOptions, filter?: PluginFilter): this; 8 | apply(compiler: Compiler): void 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | As a ____ I want ____ So that ____ 2 | 3 | ### Code samples 4 | ```css 5 | /* asset.css */ 6 | ``` 7 | 8 | ```javascript 9 | // webpack.config.js 10 | ``` 11 | 12 | ### Specifications 13 | - **CSSO Plugin version**: v0.0.0 14 | - **Webpack version**: v0.0.0 15 | - **CSSO version**: v0.0.0 *(if needed)* 16 | - **Node + npm versions**: v0.0.0 *(if needed)* 17 | 18 | (other: operating system and etc.) 19 | 20 | **/cc** @zoobestik 21 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import {Compiler} from 'webpack'; 2 | 3 | declare module 'csso-webpack-plugin' { 4 | type PluginOptions = any; 5 | type PluginFilterFn = (file: string) => boolean; 6 | type PluginFilter = PluginFilterFn | RegExp; 7 | 8 | export default class CssoWebpackPlugin { 9 | private options: PluginOptions; 10 | private filter: PluginFilterFn; 11 | 12 | public constructor(options?: PluginOptions, filter?: PluginFilter); 13 | apply(compiler: Compiler): void; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | 4 | language: node_js 5 | 6 | node_js: 7 | - "8.0.0" 8 | - "8" 9 | - "9" 10 | - "10" 11 | - "11" 12 | - "12" 13 | - "13" 14 | - "node" 15 | 16 | before_script: npm run test-prepare 17 | script: npm test 18 | 19 | branches: 20 | except: 21 | - gh-pages 22 | 23 | jobs: 24 | include: 25 | - stage: npm lint 26 | node_js: "node" 27 | script: npm run lint 28 | 29 | notifications: 30 | email: 31 | on_success: never 32 | on_failure: always 33 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/expected-v4.css: -------------------------------------------------------------------------------- 1 | .local-import-align{text-align:center}.local-a,.local-b,.local-c,.local-import-color{color:#fff} 2 | /* {"version":3,"sources":["webpack:///./import.css","webpack:///./a.css","webpack:///./b.css","webpack:///./c.css"],"names":[],"mappings":"AAAA,mB,CACI,iB,CCAJ,Q,CCDA,Q,CCAA,Q,CHIA,mB,CACI,U","file":"test.css","sourcesContent":[".import-align {\n text-align: center;\n}\n\n.import-color {\n color: #fff;\n}\n","@import url('import.css');\n.a { color: white; }\n",".b { color: #fff; }",".c { color: #ffffff; }"],"sourceRoot":""} */ 3 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/expected-v5.css: -------------------------------------------------------------------------------- 1 | .local-import-align{text-align:center}.local-a,.local-b,.local-c,.local-import-color{color:#fff} 2 | /* {"version":3,"sources":["webpack:///./import.css","webpack:///./a.css","webpack:///./b.css","webpack:///./c.css"],"names":[],"mappings":"AAAA,mB,CACI,iB,CCAJ,Q,CCDA,Q,CCAA,Q,CHIA,mB,CACI,U","file":"test.css","sourcesContent":[".import-align {\n text-align: center;\n}\n\n.import-color {\n color: #fff;\n}\n","@import url('import.css');\n.a { color: white; }\n",".b { color: #fff; }",".c { color: #ffffff; }"],"sourceRoot":""} */ 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # For more information about the configurations used 2 | # in this file, please see the EditorConfig documentation: 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | indent_size = 4 10 | indent_style = space 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | end_of_line = lf 14 | 15 | [*.md] 16 | # Markdown uses the trailing whitespaces 17 | # https://daringfireball.net/projects/markdown/syntax#block 18 | trim_trailing_whitespace = false 19 | 20 | [{package.json,*.yml}] 21 | # The indent size used in the `package.json` file cannot be changed 22 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516 23 | indent_size = 2 24 | indent_style = space 25 | -------------------------------------------------------------------------------- /test/lib/integrations/source-map-with-import/expected-v2.css: -------------------------------------------------------------------------------- 1 | .local-import-align{text-align:center}.local-a,.local-b,.local-c,.local-import-color{color:#fff} 2 | /* {"version":3,"sources":["webpack:///./import.css","webpack:///./a.css","webpack:///./b.css","webpack:///./c.css"],"names":[],"mappings":"AAAA,mB,CACI,iB,CCAJ,Q,CCDA,Q,CCAA,Q,CHIA,mB,CACI,U","file":"test.css","sourcesContent":[".import-align {\n text-align: center;\n}\n\n.import-color {\n color: #fff;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./import.css","@import url('import.css');\n.a { color: white; }\n\n\n\n// WEBPACK FOOTER //\n// ./a.css",".b { color: #fff; }\n\n\n// WEBPACK FOOTER //\n// ./b.css",".c { color: #ffffff; }\n\n\n// WEBPACK FOOTER //\n// ./c.css"],"sourceRoot":""} */ 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@babel/eslint-parser", 4 | "parserOptions": { 5 | "requireConfigFile": false, 6 | "ecmaVersion": "latest", 7 | "sourceType": "module" 8 | }, 9 | "extends": [ 10 | "airbnb-base" 11 | ], 12 | "rules": { 13 | "max-len": [ 14 | "error", 15 | 120 16 | ], 17 | 18 | "indent": [ 19 | "error", 20 | 4 21 | ], 22 | 23 | "comma-dangle": [ 24 | "error", 25 | "always-multiline" 26 | ], 27 | 28 | "arrow-parens": [ 29 | "error", 30 | "as-needed" 31 | ], 32 | 33 | "no-param-reassign": [ 34 | "error", 35 | { "props": false } 36 | ], 37 | 38 | "prefer-destructuring": "off" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 kb.chernenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to Contribute to This Project 2 | 3 | Thanks for contributing! :heart: 4 | 5 | #### **Did You Find a Bug or Any Question?** 6 | 7 | Just [open a new one](new-issue) or attach for [exist issue](issues): 8 | - For **bug** using the the label `bug`. If it is possible paste **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. *It's helps!* 9 | - For **question** using the the label `question`. 10 | - For **enhancement**: create issue **BEFORE** you start writing code and using the the label `enhancement`. 11 | 12 | #### **Did You Write a Patch That Fixes a Bug?** 13 | 14 | * Open a new GitHub pull request with the patch. 15 | 1. Fork this project 16 | 2. Create your feature branch: `git checkout -b my-new-feature` 17 | 3. Commit your changes: `git commit -am 'Add some feature'` 18 | 4. Push to the branch: `git push origin my-new-feature` 19 | 5. [Submit a pull request](new-pull-request) :tada: 20 | 21 | [//]: # (Simply change the urls below to your project information) 22 | 23 | [issues]: https://github.com/zoobestik/csso-webpack-plugin/issues 24 | [new-issue]: https://github.com/zoobestik/csso-webpack-plugin/issues/new 25 | [new-pull-request]: https://github.com/zoobestik/csso-webpack-plugin/pull/new/dev 26 | -------------------------------------------------------------------------------- /test/webpack2/webpack.config.js: -------------------------------------------------------------------------------- 1 | const DefinePlugin = require('./node_modules/webpack').DefinePlugin; 2 | const ExtractTextPlugin = require('./node_modules/extract-text-webpack-plugin'); 3 | 4 | const styleExtractPlugin = new ExtractTextPlugin({ 5 | filename: '[name].css', 6 | allChunks: false, 7 | }); 8 | 9 | module.exports = function (options) { 10 | return { 11 | entry: { 12 | test: './index.js' 13 | }, 14 | cache: false, 15 | performance: false, 16 | context: options.testDirectory, 17 | output: { 18 | filename: '[name].js', 19 | path: options.outputDirectory, 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.css$/, 25 | use: ExtractTextPlugin.extract({ 26 | use: { 27 | loader: options.basePath + '/node_modules/css-loader', 28 | options: { 29 | modules: true, 30 | localIdentName: 'local-[local]', 31 | sourceMap: true 32 | } 33 | } 34 | }), 35 | }, 36 | ], 37 | }, 38 | plugins: [ 39 | new DefinePlugin({ 40 | 'process.env.NODE_ENV': '"production"', 41 | }), 42 | styleExtractPlugin, 43 | ], 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /test/webpack4/webpack.config.js: -------------------------------------------------------------------------------- 1 | const DefinePlugin = require('webpack').DefinePlugin; 2 | const ExtractTextPlugin = require('mini-css-extract-plugin'); 3 | 4 | const styleExtractPlugin = new ExtractTextPlugin({ 5 | filename: '[name].css', 6 | }); 7 | 8 | module.exports = function (options) { 9 | return { 10 | mode: 'none', 11 | entry: { 12 | test: './index.js' 13 | }, 14 | cache: false, 15 | performance: false, 16 | context: options.testDirectory, 17 | output: { 18 | filename: '[name].js', 19 | path: options.outputDirectory, 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.css$/, 25 | use: [ 26 | ExtractTextPlugin.loader, 27 | { 28 | loader: options.basePath + '/node_modules/css-loader', 29 | options: { 30 | modules: { 31 | localIdentName: 'local-[local]', 32 | }, 33 | sourceMap: true 34 | } 35 | }, 36 | ], 37 | }, 38 | ], 39 | }, 40 | plugins: [ 41 | new DefinePlugin({ 42 | 'process.env.NODE_ENV': '"production"', 43 | }), 44 | styleExtractPlugin, 45 | ], 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /test/webpack5/webpack.config.js: -------------------------------------------------------------------------------- 1 | const DefinePlugin = require('webpack').DefinePlugin; 2 | const ExtractTextPlugin = require('mini-css-extract-plugin'); 3 | 4 | const styleExtractPlugin = new ExtractTextPlugin({ 5 | filename: '[name].css', 6 | }); 7 | 8 | module.exports = function (options, basePath) { 9 | return { 10 | mode: 'none', 11 | entry: { 12 | test: './index.js' 13 | }, 14 | cache: false, 15 | performance: false, 16 | context: options.testDirectory, 17 | output: { 18 | filename: '[name].js', 19 | path: options.outputDirectory, 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.css$/, 25 | use: [ 26 | ExtractTextPlugin.loader, 27 | { 28 | loader: options.basePath + '/node_modules/css-loader', 29 | options: { 30 | modules: { 31 | localIdentName: 'local-[local]', 32 | }, 33 | sourceMap: true 34 | } 35 | }, 36 | ], 37 | }, 38 | ], 39 | }, 40 | plugins: [ 41 | new DefinePlugin({ 42 | 'process.env.NODE_ENV': '"production"', 43 | }), 44 | styleExtractPlugin, 45 | ], 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csso-webpack-plugin", 3 | "version": "3.0.0", 4 | "author": "zoobestik ", 5 | "description": "CSSO minification files to serve your webpack bundles", 6 | "homepage": "https://github.com/zoobestik/csso-webpack-plugin", 7 | "bugs": { 8 | "url": "https://github.com/zoobestik/csso-webpack-plugin/issues" 9 | }, 10 | "keywords": [ 11 | "css", 12 | "minify", 13 | "csso", 14 | "webpack", 15 | "optimisation" 16 | ], 17 | "main": "./lib/index", 18 | "engines": { 19 | "node": "^10.13.0 || ^12.20.0 || ^14.13.0 || >=15.0.0", 20 | "npm": ">=7.0.0" 21 | }, 22 | "licenses": [ 23 | { 24 | "type": "MIT", 25 | "url": "http://www.opensource.org/licenses/mit-license.php" 26 | } 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/zoobestik/csso-webpack-plugin.git" 31 | }, 32 | "scripts": { 33 | "lint": "eslint --cache .", 34 | "test": "npm run build && jest", 35 | "ci-test": "jest", 36 | "test-prepare": "cd test/webpack2 && npm install && cd ../webpack4 && npm install && cd ../webpack5 && npm install", 37 | "build": "babel src --out-dir lib", 38 | "prepublish": "npm run build" 39 | }, 40 | "precommit": [ 41 | "lint" 42 | ], 43 | "files": [ 44 | "lib", 45 | "CHANGELOG.md", 46 | "LICENSE", 47 | "README.md" 48 | ], 49 | "typings": "lib/index.d.ts", 50 | "dependencies": { 51 | "@types/webpack": ">=4.0", 52 | "core-js": "^3.10.1", 53 | "csso": "^5.0.5", 54 | "source-map": "^0.7.3", 55 | "webpack-sources": ">=1.0.1" 56 | }, 57 | "devDependencies": { 58 | "@babel/cli": "^7.7.5", 59 | "@babel/core": "^7.7.5", 60 | "@babel/preset-env": "^7.7.6", 61 | "@babel/eslint-parser": "^7.24.0", 62 | "eslint": "^8.57.1", 63 | "eslint-config-airbnb-base": "^15.0.0", 64 | "eslint-plugin-import": "^2.29.1", 65 | "jest": "^27.5.1", 66 | "pre-commit": "^1.2.2" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/unit/filter.spec.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const CssoWebpackPlugin = require('../../lib/index').default; 3 | 4 | describe('Constructor', function() { 5 | function testDefaultOptions(plugin) { 6 | assert.deepStrictEqual(plugin.options, {}, 'default options inited'); 7 | } 8 | 9 | function testDefaultFilter(plugin) { 10 | assert.strictEqual(plugin.filter('files.css'), true, 'files.css should be true'); 11 | assert.strictEqual(plugin.filter('files.js'), false, 'files.js should be false'); 12 | assert.strictEqual(plugin.filter('/.css/file'), false, '/.css/file should be false'); 13 | } 14 | 15 | it('default', function() { 16 | const plugin = new CssoWebpackPlugin(); 17 | testDefaultOptions(plugin); 18 | testDefaultFilter(plugin); 19 | }); 20 | 21 | it('with options', function() { 22 | const options = { test: 1 }; 23 | const plugin = new CssoWebpackPlugin(options); 24 | assert.deepStrictEqual(plugin.options, { test: 1 }, 'single argument with options'); 25 | assert.deepStrictEqual(options, { test: 1 }, 'single argument with options immutable'); 26 | testDefaultFilter(plugin); 27 | }); 28 | 29 | it('with regexp filter', function() { 30 | const plugin = new CssoWebpackPlugin(/^text/gi); 31 | 32 | testDefaultOptions(plugin); 33 | assert.strictEqual(plugin.filter('text-file.pcss'), true, 'text-file.pcss should be true'); 34 | assert.strictEqual(plugin.filter('files.js'), false, 'files.js should be false'); 35 | assert.strictEqual(plugin.filter('/.css/text'), false, '/.css/text should be false'); 36 | 37 | assert.doesNotThrow( 38 | function() { new CssoWebpackPlugin({}, /^text/gi); }, 39 | 'second argument should be approved' 40 | ); 41 | }); 42 | 43 | it('with function filter', function() { 44 | const filter = function() { return true; }; 45 | 46 | const oneArgPlugin = new CssoWebpackPlugin(filter); 47 | testDefaultOptions(oneArgPlugin); 48 | assert.strictEqual(oneArgPlugin.filter, filter, 'single `filter` should be set to this.filter'); 49 | 50 | const twoArgPlugin = new CssoWebpackPlugin({}, filter); 51 | assert.strictEqual(twoArgPlugin.filter, filter, 'second argument `filter` should be set to this.filter'); 52 | }); 53 | 54 | it('unexpected type for filter', function() { 55 | const expectedMessage = 'Error: filter should be one of these types: function, regexp, undefined'; 56 | 57 | function isExpectedError(err) { 58 | return (err instanceof Error) && err.toString() === expectedMessage; 59 | } 60 | 61 | function createPlugin() { 62 | new CssoWebpackPlugin({}, 'some text'); 63 | } 64 | 65 | assert.throws(createPlugin, isExpectedError, 'throw error rise unexpected type'); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/lib/utils.js: -------------------------------------------------------------------------------- 1 | const existsSync = require('fs').existsSync; 2 | const lstatSync = require('fs').lstatSync; 3 | const rmdirSync = require('fs').rmdirSync; 4 | const readdirSync = require('fs').readdirSync; 5 | const unlinkSync = require('fs').unlinkSync; 6 | const join = require('path').join; 7 | 8 | /** 9 | * Set test timeout in a way that works for both Jest and Mocha. 10 | * 11 | * @param {any} ctx - The test context (e.g., `this` inside describe/it for Mocha). Optional for Jest. 12 | * @param {number} ms - Timeout in milliseconds. Defaults to 5000. 13 | */ 14 | function setTestTimeout(ctx, ms = 5000) { 15 | // Jest: global `jest` with `setTimeout` function 16 | if (typeof jest !== 'undefined' && jest && typeof jest.setTimeout === 'function') { 17 | jest.setTimeout(ms); 18 | return; 19 | } 20 | 21 | const context = ctx || (typeof this !== 'undefined' ? this : undefined); 22 | 23 | if (context && typeof context.timeout === 'function') { 24 | context.timeout(ms); 25 | return; 26 | } 27 | 28 | throw Error('Unable to set test timeout'); 29 | } 30 | 31 | /** 32 | * Recursively and forcefully removes a directory or file. 33 | * @param {string} targetPath - The path to the file or directory to delete. 34 | */ 35 | function deleteRecursiveSync(targetPath) { 36 | // Check if the path exists 37 | if (!existsSync(targetPath)) { 38 | return; // Nothing to delete 39 | } 40 | 41 | // We'll do an iterative post-order traversal to avoid deep recursion. 42 | // Use a stack for directories to process; each entry: { path, visited } 43 | const stack = [{ path: targetPath, visited: false }]; 44 | 45 | while (stack.length) { 46 | const current = stack.pop(); 47 | const curPath = current.path; 48 | 49 | // If path was removed during earlier ops, skip safely 50 | if (!existsSync(curPath)) continue; 51 | 52 | const stats = lstatSync(curPath); // lstat: don't follow symlinks 53 | 54 | if (!stats.isDirectory()) { 55 | // File or symlink — delete immediately 56 | unlinkSync(curPath); 57 | continue; 58 | } 59 | 60 | if (!current.visited) { 61 | // First time we see this directory: push back as visited, 62 | // then push its children to ensure children are removed first 63 | stack.push({ path: curPath, visited: true }); 64 | 65 | // Read children and schedule them for deletion 66 | const files = readdirSync(curPath); 67 | for (let i = 0; i < files.length; i++) { 68 | stack.push({ path: join(curPath, files[i]), visited: false }); 69 | } 70 | } else { 71 | // All children should be deleted by now — remove the directory itself 72 | rmdirSync(curPath); 73 | } 74 | } 75 | } 76 | 77 | module.exports = { 78 | setTestTimeout, 79 | deleteRecursiveSync, 80 | }; 81 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "master", "dev" ] 6 | pull_request: 7 | branches: [ "master", "dev" ] 8 | schedule: 9 | # Run every night at 02:00 UTC, workflow itself will restrict to master via branch check in job if needed 10 | - cron: '0 2 * * *' 11 | 12 | jobs: 13 | build_pkg: 14 | name: Build package on stable Node 15 | if: ${{ (github.event_name != 'pull_request' || github.event.pull_request.draft == false) && (github.event_name != 'schedule' || github.ref == 'refs/heads/master') }} 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup Node.js lts/* (stable) 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 'lts/*' 25 | 26 | - name: Install dependencies 27 | run: npm i 28 | 29 | - name: Lint sources 30 | run: npm run lint 31 | 32 | - name: Build library 33 | run: npm run build 34 | 35 | - name: Prepare integration test fixtures 36 | run: npm run test-prepare 37 | 38 | - name: Build library 39 | run: npm run ci-test 40 | 41 | - name: Upload package artifact 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: built-libs 45 | path: lib/ 46 | 47 | test: 48 | name: Test on Node ${{ matrix.node-version }} / ${{ matrix.os }} 49 | needs: build_pkg 50 | if: ${{ (github.event_name != 'pull_request' || github.event.pull_request.draft == false) && (github.event_name != 'schedule' || github.ref == 'refs/heads/master') }} 51 | runs-on: ${{ matrix.os }} 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | os: [ ubuntu-latest, windows-latest ] 56 | node-version: 57 | - '10' 58 | - '12' 59 | - '14' 60 | - '16' 61 | - '18' 62 | - '20' 63 | - '22' 64 | - 'node' # latest current 65 | 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v4 69 | 70 | - name: Setup Node.js ${{ matrix.node-version }} 71 | uses: actions/setup-node@v4 72 | with: 73 | node-version: ${{ matrix.node-version }} 74 | 75 | - name: Ensure npm v7+ 76 | shell: bash 77 | # language=bash 78 | run: | 79 | CURRENT_NPM=$(npm -v || echo 0) 80 | MAJOR=${CURRENT_NPM%%.*} 81 | echo "Detected npm version: $CURRENT_NPM" 82 | if [ "${MAJOR:-0}" -lt 7 ]; then 83 | echo "Upgrading npm to v7..." 84 | npm i -g npm@7 85 | echo "npm upgraded to: $(npm -v)" 86 | else 87 | echo "npm is already v7+" 88 | fi 89 | 90 | - name: Download built libs artifact 91 | uses: actions/download-artifact@v4 92 | with: 93 | name: built-libs 94 | path: ./lib 95 | 96 | - name: Install dependencies 97 | run: npm i 98 | 99 | - name: Prepare integration test fixtures 100 | run: npm run test-prepare 101 | 102 | - name: Run tests 103 | run: npm run ci-test 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSSO Webpack Plugin 2 | [![npm][npm]][npm-url] 3 | [![node][node]][node-url] 4 | [![GitHub Actions Build Status](https://github.com/zoobestik/csso-webpack-plugin/actions/workflows/test.yml/badge.svg)](https://github.com/zoobestik/csso-webpack-plugin/actions/workflows/test.yml) 5 | [![downloads](https://img.shields.io/npm/dm/csso-webpack-plugin.svg)](http://npm-stats.com/~packages/csso-webpack-plugin) 6 | 7 | Logo 8 | 9 | **Why is not [csso-loader](https://www.npmjs.com/package/csso-loader) or [postcss-csso](https://github.com/lahmatiy/postcss-csso)?** 10 | * **[Full restructuring](https://rawgithub.com/zoobestik/csso-webpack-plugin/dev/docs/img/better-full.svg)** in bundles: 11 | ![better](https://rawgithub.com/zoobestik/csso-webpack-plugin/dev/docs/img/better.svg) 12 | 13 | * No problems with custom syntax like **css-modules** – `:global(.c .d) .a { color: #fff; }` 14 | syntax 15 | 16 | * Possible to generate both **pure and minimized** versions [at the same time](#options). 17 | 18 | ## Install 19 | ```bash 20 | npm i -D csso-webpack-plugin 21 | ``` 22 | 23 | For **`node` 8.0.0 > < 10.0.0** use [2.x](https://github.com/zoobestik/csso-webpack-plugin/tree/v1) version with `csso@^4`: 24 | ```bash 25 | npm i -D csso-webpack-plugin@2 26 | ``` 27 | 28 | For **`node` < 8.0.0** use [1.x](https://github.com/zoobestik/csso-webpack-plugin/tree/v1) version with `csso@^3`: 29 | ```bash 30 | npm i -D csso-webpack-plugin@1 31 | ``` 32 | 33 | ## Usage 34 | Plugin good to use in pair with [ExtractTextPlugin](https://github.com/webpack-contrib/extract-text-webpack-plugin) or [MiniCssExtractPlugin](https://github.com/webpack-contrib/mini-css-extract-plugin). 35 | ```js 36 | const CssoWebpackPlugin = require('csso-webpack-plugin').default; 37 | 38 | module.exports = { 39 | module: { /* ... */ }, 40 | plugins: [ 41 | new MiniCssExtractPlugin({ 42 | filename: "[name].css", 43 | chunkFilename: "[id].css" 44 | }), 45 | new CssoWebpackPlugin(), 46 | ] 47 | } 48 | ``` 49 | 50 | ## Options 51 | 52 | ```js 53 | new CssoWebpackPlugin([options: CssoOptions], [filter: function | RegExp]) 54 | ``` 55 | 56 | Arguments: 57 | * `options` — [csso options](https://github.com/css/csso#minifysource-options). 58 | * `options.pluginOutputPostfix` — *function(file)* or *string postfix*, if passed, plugin will create two assets vanilla and compressed. 59 | Example: 60 | ```javascript 61 | { 62 | plugins: [ 63 | new ExtractTextPlugin('test.css'), 64 | new CssoWebpackPlugin({ pluginOutputPostfix: 'min' }) 65 | /* Generated: 66 | test.css — uncompressed file 67 | test.min.css — minimized with csso file 68 | */ 69 | ] 70 | } 71 | ``` 72 | * `filter` — Detect should be file processed. Defaults: *to ends with `.css`*. 73 | 74 | ## Flow support 75 | I don't now why, but plugin ships with flow typings (*typedef too*). To use them in your project, add this to the `[libs]` section of your `.flowconfig`: 76 | ``` 77 | [libs] 78 | node_modules/csso-webpack-plugin/lib/index.js.flow 79 | ``` 80 | 81 | ## Acknowledgements 82 | [![Develop By](https://img.shields.io/badge/develop%20by-zoobestik-blue.svg?style=flat)](https://ru.linkedin.com/in/kbchernenko) [![Logo By](https://img.shields.io/badge/logo%20by-@egorii-yellow.svg?style=flat)](https://www.linkedin.com/in/%D0%B5%D0%B3%D0%BE%D1%80-%D0%B0%D0%BB%D0%B5%D0%BA%D1%81%D0%B5%D0%B5%D0%B2-968a1265/) [![MIT license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) 83 | 84 | [npm]: https://img.shields.io/npm/v/csso-webpack-plugin.svg 85 | [npm-url]: https://npmjs.com/package/csso-webpack-plugin 86 | 87 | [node]: https://img.shields.io/node/v/csso-webpack-plugin.svg 88 | [node-url]: https://nodejs.org 89 | 90 | [deps]: https://david-dm.org/zoobestik/csso-webpack-plugin.svg 91 | [deps-url]: https://david-dm.org/zoobestik/csso-webpack-plugin 92 | 93 | [tests]: http://img.shields.io/travis/zoobestik/csso-webpack-plugin.svg 94 | [tests-url]: https://travis-ci.org/zoobestik/csso-webpack-plugin 95 | 96 | [cover]: https://coveralls.io/repos/github/zoobestik/csso-webpack-plugin/badge.svg 97 | [cover-url]: https://coveralls.io/github/zoobestik/csso-webpack-plugin 98 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 3.0.0 (August 20, 2025) 4 | 5 | Release 3.0.0: 6 | 7 | * Update CSSO to a new major version 5.x (closes [#31](https://github.com/zoobestik/csso-webpack-plugin/issues/31)) 8 | * Drop support for Node.js < 10 9 | 10 | ## 2.0.0-beta.3 (April 9, 2021) 11 | 12 | Release candidate N2.3: 13 | 14 | * Move core-js from dev to runtime dependencies (closes [#28](https://github.com/zoobestik/csso-webpack-plugin/issues/28), [#29](https://github.com/zoobestik/csso-webpack-plugin/issues/29)) 15 | 16 | ## 2.0.0-beta.2 (April 1, 2021) 17 | 18 | Release candidate N2.2: 19 | 20 | * Support new process assets in webpack 5 [#24](https://github.com/zoobestik/csso-webpack-plugin/issues/24) (posted by [@dotnetshadow](https://github.com/dotnetshadow)) 21 | * Fix types for Webpack Plugin [#18](https://github.com/zoobestik/csso-webpack-plugin/issues/18) (fixed by [Mikhail Bodrov](https://github.com/Connormiha)) 22 | 23 | ## 2.0.0-beta.1 (December 12, 2019) 24 | 25 | Release candidate N2.1: 26 | 27 | * Update for [CSSO 4](https://github.com/css/csso/releases/tag/v4.0.0) 28 | * Drop maintenance for **NODE < 8.0.0** (see `csso v4` release notes, it's incompatible) 29 | 30 | Resolved: 31 | [#15](https://github.com/zoobestik/csso-webpack-plugin/issues/15), 32 | [#16](https://github.com/zoobestik/csso-webpack-plugin/issues/16), 33 | [#22](https://github.com/zoobestik/csso-webpack-plugin/issues/22). 34 | 35 | Chores: 36 | * use native `async/await` instead [async](https://www.npmjs.com/package/async) 37 | * moved to [`source-map`](https://github.com/mozilla/source-map/blob/master/CHANGELOG.md#070) with support WEBAssembly 38 | * Up-to-date dev-dependencies versions 39 | 40 | ## 1.0.0-beta.12 (April 4, 2018) 41 | 42 | Release candidate N9: 43 | 44 | * Fix warning with webpack 4.x (thanks for [@the-spyke](https://github.com/the-spyke)): 45 | ``` 46 | DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead 47 | ``` 48 | 49 | ## 1.0.0-beta.11 (April 4, 2018) 50 | 51 | * No changes 52 | 53 | ## 1.0.0-beta.10 (November 4, 2017) 54 | 55 | Release candidate N8: 56 | 57 | * Add new option for two different files — [`pluginOutputPostfix`](https://github.com/zoobestik/csso-webpack-plugin/commit/7da22c9c34c2172148049912589b507d7309a852#diff-04c6e90faac2675aa89e2176d2eec7d8R42) 58 | * **csso** 3.3.0 -> 3.4.0 by default 59 | * Update-to-date dev-dependencies versions 60 | 61 | ## 1.0.0-beta.9 (October 17, 2017) 62 | 63 | Release candidate N7: 64 | 65 | * **csso** 3.2.0 -> 3.3.0 by default 66 | * Update-to-date dev-dependencies versions 67 | 68 | ## 1.0.0-beta.8 (August 28, 2017) 69 | 70 | Release candidate N6: 71 | 72 | * Update-to-date dependencies versions 73 | * **BREAKING**: `options.sourceMap` is deprecate and should be removed from config. 74 | For getting source map just use any value [`devtool`](https://webpack.js.org/configuration/devtool/#devtool). 75 | 76 | ## 1.0.0-beta.7 (June 3, 2017) 77 | 78 | Release candidate N5: 79 | 80 | * Trivial; strict 0.10 node version 81 | * Update-to-date dependencies versions 82 | 83 | ## 1.0-beta.6 (April 17, 2017) 84 | 85 | Release candidate N4 86 | 87 | * **trivial**. Provide typings for Flow and Typescript 88 | 89 | ## 1.0-beta.5 (April 15, 2017) 90 | 91 | Release candidate N3 92 | 93 | * Fix issue with regexp filters 94 | 95 | ## 1.0-beta.4 (April 10, 2017) 96 | 97 | Release candidate N2 98 | 99 | * Add `output.sourceMapFilename` options support 100 | 101 | ## 1.0-beta.3 (April 9, 2017) 102 | 103 | Release candidate N1 104 | 105 | * **Bugfix**: simple compilation config triggered success callback twice 106 | 107 | ### Breaking changes 108 | * The `sourceMap:true` used `devtool` option if chunk detection unexpected 109 | * nodejs is **less than version 4** still can be used but **isn't tested now**. 110 | 111 | ## 1.0-beta.2 (March 31, 2017) 112 | 113 | Strict source map type from options 114 | 115 | * `options.sourceMap` is enum and **required** for generate source maps. 116 | * Add strong dependencies for *[yarn](https://yarnpkg.com/en/) package manager*. 117 | 118 | ## 1.0-beta.1 (March 30, 2017) 119 | 120 | Source Maps feature supported 121 | 122 | * Initial **source map** feature implementation 123 | * Update-to-date dependencies versions 124 | 125 | ## 1.0-alpha.4 (March 7, 2017) 126 | 127 | Acknowledgements and regex filter 128 | 129 | * Add “Acknowledgements” section to README.md 130 | * Single argument regex typed should be filter 131 | 132 | ## 1.0-alpha.3 (March 6, 2017) 133 | 134 | Documentation and draft filter API improves 135 | 136 | * Add more additional information about motivation 137 | * Filter function may be single now: `new CSSOCompressPlugin(file => file.startsWith('foo'))` 138 | 139 | ## 1.0-alpha (March 6, 2017) 140 | 141 | Simple draft for minify css bundles with full restructing rules by [csso](https://github.com/csso/csso). 142 | 143 | **No source maps** 144 | **No exception pretty print** 145 | **No anything else except basic minify** 146 | -------------------------------------------------------------------------------- /test/lib/integrations.js: -------------------------------------------------------------------------------- 1 | Object.defineProperty(require('os'), 'EOL', { 2 | value: '\n' 3 | }); 4 | 5 | const fs = require('fs'); 6 | const join = require('path').join; 7 | const assert = require('assert'); 8 | const deleteRecursiveSync = require("./utils").deleteRecursiveSync; 9 | const setTestTimeout = require('./utils').setTestTimeout; 10 | 11 | const root = join(__dirname, 'integrations'); 12 | 13 | function runIntegrations(basePath, version) { 14 | setTestTimeout(this, 5000); 15 | 16 | const output = join(basePath, '_out'); 17 | 18 | deleteRecursiveSync(output); 19 | 20 | const nodeVersions = process.version.split('.'); 21 | const nodeMajorVersion = parseInt(nodeVersions[0].substring(1), 10); 22 | const nodeMinorVersion = parseInt(nodeVersions[1], 10); 23 | 24 | const isEnvCorrect = !( 25 | (version === '4' && nodeMajorVersion === 8 && nodeMinorVersion === 0) || 26 | (version === '5' && nodeMajorVersion < 10) 27 | ); 28 | 29 | if (!isEnvCorrect) { 30 | it('webpack ' + version + ' unsuppported with node ' + process.version, function () { 31 | assert.ok(true); 32 | }); 33 | return; 34 | } 35 | 36 | const cases = fs.readdirSync(root); 37 | const webpack = require(join(basePath, 'node_modules', 'webpack')); 38 | const getWebpackConfig = require(join(basePath, 'webpack.config.js')); 39 | 40 | cases.forEach(function (testCase) { 41 | it('with ' + testCase + ' test', function () { 42 | return new Promise(function (resolve, reject) { 43 | const outputDirectory = join(output, testCase); 44 | const testDirectory = join(root, testCase); 45 | const configFile = join(testDirectory, 'webpack.config.js'); 46 | 47 | let options = getWebpackConfig({ 48 | basePath: basePath, 49 | outputDirectory: outputDirectory, 50 | testDirectory: testDirectory 51 | }); 52 | 53 | if (fs.existsSync(configFile)) { 54 | const testConfig = require(configFile)({version}); 55 | options = Object.assign({}, options, testConfig, { 56 | output: Object.assign({}, options.output, testConfig.output), 57 | plugins: options.plugins.concat(testConfig.plugins) 58 | }); 59 | } 60 | 61 | webpack(options, function (err, stats) { 62 | if (err) return reject(err); 63 | if (stats.hasErrors()) return reject(new Error(stats.toString())); 64 | 65 | const expectedCssExt = 'expected-v' + version + '.css'; 66 | 67 | fs.readdir(testDirectory, function (err, files) { 68 | if (err) return reject(err); 69 | 70 | files = files.filter(name => name.endsWith(expectedCssExt)); 71 | 72 | assert.ok(files.length > 0, 'Integration test should be with css file'); 73 | 74 | files.forEach(name => { 75 | const prefix = name.substring(0, name.length - expectedCssExt.length) || ''; 76 | const actualName = 'test.' + prefix + 'css'; 77 | 78 | const actual = fs.readFileSync(join(outputDirectory, actualName), 'utf-8') 79 | .replace(/\r\n/g, '\n') 80 | .replace(/\n$/g, '') 81 | .replace(/\/\*# sourceMappingURL=data:application\/json;charset=utf-8;base64,(?.+)\*\//gi, (_, hash) => { 82 | let text = Buffer.from(hash, "base64").toString("utf8"); 83 | 84 | if (version === '2' && process.platform === 'win32') { 85 | const pattern = testDirectory 86 | .replace(/\\/g, '/') 87 | .replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 88 | 89 | text = text.replace(new RegExp(pattern, 'gi'), '.'); 90 | } 91 | 92 | return `/* ${text} */`; 93 | }); 94 | 95 | 96 | const expected = fs.readFileSync(join(testDirectory, name), 'utf-8') 97 | .replace(/\r\n/g, '\n') 98 | .replace(/%%unit-hash%%/g, stats.hash) 99 | .replace(/\n$/g, ''); 100 | 101 | assert.strictEqual(actual, expected, 102 | 'Output ' + testCase + ' — ' + name + ' file isn\'t equals ' + actualName 103 | ); 104 | }); 105 | 106 | resolve(); 107 | }); 108 | }); 109 | }); 110 | }); 111 | }); 112 | } 113 | 114 | module.exports = runIntegrations; 115 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import csso from 'csso'; 3 | import RawSource from 'webpack-sources/lib/RawSource'; 4 | import SourceMapSource from 'webpack-sources/lib/SourceMapSource'; 5 | import { SourceMapConsumer } from 'source-map'; 6 | 7 | const filterDefault = file => file.endsWith('.css'); 8 | const createRegexpFilter = regex => str => regex.test(str); 9 | const isFilterType = inst => typeof inst === 'function' || inst instanceof RegExp; 10 | 11 | const getOutputAssetFilename = postfix => file => { 12 | const parsed = path.parse(file); 13 | parsed.ext = `.${postfix}${parsed.ext}`; 14 | parsed.base = `${parsed.name}${parsed.ext}`; 15 | return path.format(parsed); 16 | }; 17 | 18 | const pluginName = 'csso-webpack-plugin'; 19 | 20 | const unCamelCase = str => str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`); 21 | 22 | /* 23 | New webpack 4 API, 24 | for webpack 2-3 compatibility used .plugin('...', cb) 25 | */ 26 | const pluginCompatibility = (caller, hook, tapAction, cb) => { 27 | if (caller.hooks) { 28 | caller.hooks[hook][tapAction](pluginName, cb); 29 | return; 30 | } 31 | 32 | caller.plugin(unCamelCase(hook), cb); 33 | }; 34 | 35 | const tapOptimizeChunkAssets = (compiler, compilation, cb) => { 36 | /* 37 | New webpack 5 API, 38 | for webpack 4 are using optimizeChunkAssets 39 | */ 40 | if (compilation.hooks && compilation.hooks.processAssets) { 41 | compilation.hooks.processAssets.tapPromise( 42 | { 43 | name: pluginName, 44 | stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE, 45 | additionalAssets: true, 46 | }, 47 | assets => cb(Object.keys(assets)), 48 | ); 49 | return; 50 | } 51 | 52 | pluginCompatibility(compilation, 'optimizeChunkAssets', 'tapAsync', (chunks, done) => { 53 | Promise.all( 54 | chunks.map(chunk => cb(chunk.files)), 55 | ) 56 | /* it's important not to pass any args inside `done` 57 | NOT: .then(done), ONLY: .then(() => done()) */ 58 | .then(() => done()); 59 | }); 60 | }; 61 | 62 | const getAsset = (compilation, name) => { 63 | if (compilation.getAsset) { 64 | const { source } = compilation.getAsset(name); 65 | return source; 66 | } 67 | 68 | return compilation.assets[name]; 69 | }; 70 | 71 | export default class CssoWebpackPlugin { 72 | constructor(opts, filter) { 73 | this.options = opts; 74 | this.filter = filter; 75 | this.pluginOutputPostfix = null; 76 | 77 | if (isFilterType(opts) && filter === undefined) { 78 | this.filter = opts; 79 | this.options = undefined; 80 | } 81 | 82 | if (this.filter === undefined) { 83 | this.filter = filterDefault; 84 | } 85 | 86 | if (this.filter instanceof RegExp) { 87 | this.filter = createRegexpFilter(this.filter); 88 | } 89 | 90 | if (typeof this.filter !== 'function') { 91 | throw new Error('filter should be one of these types: function, regexp, undefined'); 92 | } 93 | 94 | const { pluginOutputPostfix, ...options } = this.options || {}; 95 | 96 | if (pluginOutputPostfix) { 97 | this.pluginOutputPostfix = typeof pluginOutputPostfix === 'function' 98 | ? pluginOutputPostfix 99 | : getOutputAssetFilename(pluginOutputPostfix); 100 | } 101 | 102 | this.options = options; 103 | } 104 | 105 | apply(compiler) { 106 | pluginCompatibility(compiler, 'compilation', 'tap', compilation => { 107 | const options = this.options; 108 | const { pluginOutputPostfix } = this; 109 | 110 | const compress = assets => Promise.all(assets.map(async name => { 111 | try { 112 | if (!this.filter(name)) { return; } 113 | 114 | let source; 115 | let sourceMap; 116 | 117 | const asset = getAsset(compilation, name); 118 | 119 | if (asset.sourceAndMap) { 120 | const sourceAndMap = asset.sourceAndMap(); 121 | sourceMap = sourceAndMap.map; 122 | source = sourceAndMap.source; 123 | } else { 124 | sourceMap = asset.map(); 125 | source = asset.source(); 126 | } 127 | 128 | if (Buffer.isBuffer(source)) { 129 | source = source.toString('utf-8'); 130 | } 131 | 132 | if (options.sourceMap !== undefined) { 133 | compilation.warnings.push(new Error('CssoWebpackPlugin: ' 134 | + '“sourceMap” option is DEPRECATED. ' 135 | + 'Use webpack “devtool” instead.\n\tFor more info about the usage see ' 136 | + 'https://github.com/zoobestik/csso-webpack-plugin/releases/tag/v1.0.0-beta.8')); 137 | } 138 | 139 | let fileOutput = name; 140 | 141 | if (pluginOutputPostfix) { 142 | fileOutput = pluginOutputPostfix(name); 143 | } 144 | 145 | let { css, map } = csso.minify(source, { // eslint-disable-line prefer-const 146 | ...options, 147 | filename: fileOutput, 148 | sourceMap: Boolean(compiler.options.devtool), 149 | }); 150 | 151 | if (map && sourceMap) { 152 | const consumerMap = await new SourceMapConsumer(sourceMap); 153 | map.applySourceMap(consumerMap, fileOutput); 154 | } 155 | 156 | if (!map) { 157 | map = sourceMap; 158 | } 159 | 160 | compilation.assets[fileOutput] = map 161 | ? new SourceMapSource(css, fileOutput, map.toJSON ? map.toJSON() : map) 162 | : new RawSource(css); 163 | } catch (err) { 164 | const prefix = `${name} from CssoWebpackPlugin\n`; 165 | const { message, parseError, stack } = err; 166 | let error = `${message} ${stack}`; 167 | 168 | if (parseError) { 169 | error = `${message} [${name}:${parseError.line}:${parseError.column}]`; 170 | } 171 | 172 | compilation.errors.push(new Error(`${prefix}${error}`)); 173 | } 174 | })); 175 | 176 | tapOptimizeChunkAssets(compiler, compilation, compress); 177 | }); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /docs/img/better.svg: -------------------------------------------------------------------------------- 1 | 2 |
JS File
<b>JS File</b>
a.css
.a {  color: #fff; }
[Not supported by viewer]
b.css
.b {  color: white; }
[Not supported by viewer]
c.css
.c { color: #FFFFFF; }
[Not supported by viewer]
css-loader
css-loader
css-loader
css-loader
Extract Text
Plugin
[Not supported by viewer]

<div><br></div>
style.css
(20 bytes)

50% better

.a,.b,.c{ color:#fff }
[Not supported by viewer]
Csso Webpack
Plugin

(optimize)
[Not supported by viewer]
-------------------------------------------------------------------------------- /docs/img/better-full.svg: -------------------------------------------------------------------------------- 1 | 2 |
JS File
<b>JS File</b>
a.css
.a {  color: #fff; }
[Not supported by viewer]
b.css
.b {  color: white; }
[Not supported by viewer]
c.css
.c { color: #FFFFFF; }
[Not supported by viewer]
css-loader
css-loader
css-loader
css-loader
Extract Text
Plugin
[Not supported by viewer]
csso-loader
(optimize)
[Not supported by viewer]
style.css
(42 bytes)

.a {  color: #fff; }
.b {  color: #fff; }
.c {  color: #fff; }
[Not supported by viewer]
JS File
<b>JS File</b>
a.css
.a {  color: #fff; }
[Not supported by viewer]
b.css
.b {  color: white; }
[Not supported by viewer]
c.css
.c { color: #FFFFFF; }
[Not supported by viewer]
css-loader
css-loader
css-loader
css-loader
Extract Text
Plugin
[Not supported by viewer]

<div><br></div>
style.css
(20 bytes)

50% better

.a,.b,.c{ color:#fff }
[Not supported by viewer]
Csso Webpack
Plugin

(optimize)
[Not supported by viewer]
--------------------------------------------------------------------------------