├── .husky ├── pre-commit └── commit-msg ├── test ├── fixtures │ ├── config-autoload │ │ ├── empty │ │ │ ├── .postcssrc │ │ │ ├── fixtures │ │ │ │ ├── imports │ │ │ │ │ ├── section.sss │ │ │ │ │ └── section.css │ │ │ │ ├── index.sss │ │ │ │ └── index.css │ │ │ └── expect │ │ │ │ ├── index.css │ │ │ │ └── index.sss │ │ ├── err │ │ │ ├── plugins │ │ │ │ ├── plugin.js │ │ │ │ ├── .postcssrc.js │ │ │ │ ├── object │ │ │ │ │ ├── .postcssrc.js │ │ │ │ │ └── options │ │ │ │ │ │ └── .postcssrc.js │ │ │ │ └── array │ │ │ │ │ ├── .postcssrc.js │ │ │ │ │ └── options │ │ │ │ │ └── .postcssrc.js │ │ │ └── options │ │ │ │ ├── parser │ │ │ │ └── .postcssrc.js │ │ │ │ ├── syntax │ │ │ │ └── .postcssrc.js │ │ │ │ └── stringifier │ │ │ │ └── .postcssrc.js │ │ ├── pkg │ │ │ ├── imports │ │ │ │ └── section.css │ │ │ ├── index.js │ │ │ ├── index.css │ │ │ └── package.json │ │ ├── rc-js │ │ │ ├── fixtures │ │ │ │ ├── imports │ │ │ │ │ ├── section.sss │ │ │ │ │ └── section.css │ │ │ │ ├── index.sss │ │ │ │ └── index.css │ │ │ ├── expect │ │ │ │ ├── index.css │ │ │ │ └── index.sss │ │ │ └── .postcssrc.js │ │ ├── rc │ │ │ ├── fixtures │ │ │ │ ├── imports │ │ │ │ │ ├── section.sss │ │ │ │ │ └── section.css │ │ │ │ ├── index.sss │ │ │ │ └── index.css │ │ │ ├── expect │ │ │ │ ├── index.sss │ │ │ │ └── index.css │ │ │ └── .postcssrc │ │ ├── js │ │ │ ├── object │ │ │ │ ├── imports │ │ │ │ │ └── section.css │ │ │ │ ├── index.js │ │ │ │ ├── index.css │ │ │ │ └── postcss.config.js │ │ │ ├── array │ │ │ │ ├── imports │ │ │ │ │ └── section.css │ │ │ │ ├── index.js │ │ │ │ ├── index.css │ │ │ │ └── postcss.config.js │ │ │ ├── array-mjs │ │ │ │ ├── imports │ │ │ │ │ └── section.css │ │ │ │ ├── index.js │ │ │ │ ├── index.css │ │ │ │ └── postcss.config.mjs │ │ │ └── array-esm-js │ │ │ │ ├── imports │ │ │ │ └── section.css │ │ │ │ ├── index.js │ │ │ │ ├── index.css │ │ │ │ └── postcss.config.js │ │ └── ts │ │ │ ├── object │ │ │ ├── imports │ │ │ │ └── section.css │ │ │ ├── index.js │ │ │ ├── index.css │ │ │ └── postcss.config.ts │ │ │ ├── array │ │ │ ├── imports │ │ │ │ └── section.css │ │ │ ├── index.js │ │ │ ├── index.css │ │ │ └── postcss.config.ts │ │ │ └── array-mts │ │ │ ├── imports │ │ │ └── section.css │ │ │ ├── index.js │ │ │ ├── index.css │ │ │ └── postcss.config.mts │ ├── less │ │ ├── style.less │ │ └── index.js │ ├── scss │ │ ├── style.scss │ │ └── index.js │ ├── sss │ │ ├── style.sss │ │ └── index.js │ ├── package-json-files │ │ ├── no-postcss │ │ │ └── package.json │ │ ├── postcss-v8-in-dependencies │ │ │ └── package.json │ │ └── postcss-v8-in-devDependencies │ │ │ └── package.json │ ├── jss │ │ ├── postcss-js │ │ │ ├── style.js │ │ │ └── index.js │ │ └── exec │ │ │ ├── index.js │ │ │ └── style.exec.js │ ├── config-scope │ │ ├── css │ │ │ ├── invalid.config.js │ │ │ ├── index.js │ │ │ ├── index2.js │ │ │ ├── style.css │ │ │ ├── style2.css │ │ │ ├── custom.config.js │ │ │ └── plugins.config.js │ │ ├── with-config │ │ │ ├── index.js │ │ │ ├── postcss.config.js │ │ │ └── style.css │ │ ├── postcss.config.js │ │ ├── config │ │ │ ├── postcss.config.js │ │ │ ├── context │ │ │ │ ├── postcss.config.js │ │ │ │ └── plugin.js │ │ │ └── plugin.js │ │ ├── from-to │ │ │ └── postcss.config.js │ │ ├── all-options │ │ │ └── postcss.config.js │ │ └── api │ │ │ └── postcss.config.js │ ├── css │ │ ├── index.js │ │ ├── plugins.config.js │ │ └── style.css │ ├── esparser │ │ ├── index.mjs │ │ ├── package.json │ │ └── runManual.mjs │ └── plugin │ │ ├── new-api.plugin.js │ │ ├── other-plugin.js │ │ ├── plugin.js │ │ └── default-other-plugin.js ├── helpers │ ├── getWarnings.js │ ├── getErrors.js │ ├── compile.js │ ├── testLoader.js │ ├── readAssets.js │ ├── getExecutedCode.js │ ├── execute.js │ ├── getCodeFromBundle.js │ ├── index.js │ ├── readAsset.js │ ├── normalizeErrors.js │ ├── astLoader.js │ └── getCompiler.js ├── cjs.test.js ├── __snapshots__ │ ├── execute.test.js.snap │ ├── implementation.test.js.snap │ ├── loader.test.js.snap │ ├── validate-options.test.js.snap │ └── sourceMap.test.js.snap ├── implementation.test.js ├── execute.test.js ├── validate-options.test.js ├── config-autoload.test.js ├── loader.test.js └── sourceMap.test.js ├── src ├── cjs.js ├── config.d.ts ├── options.json ├── index.js └── utils.js ├── .gitattributes ├── .prettierignore ├── lint-staged.config.js ├── commitlint.config.js ├── eslint.config.mjs ├── .editorconfig ├── .gitignore ├── babel.config.js ├── .github └── workflows │ ├── dependency-review.yml │ └── nodejs.yml ├── jest.config.js ├── .cspell.json ├── LICENSE ├── package.json └── CHANGELOG.md /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | lint-staged 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | commitlint --edit $1 2 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/empty/.postcssrc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/less/style.less: -------------------------------------------------------------------------------- 1 | a { color: coral } 2 | -------------------------------------------------------------------------------- /test/fixtures/scss/style.scss: -------------------------------------------------------------------------------- 1 | a { color: coral } 2 | -------------------------------------------------------------------------------- /test/fixtures/sss/style.sss: -------------------------------------------------------------------------------- 1 | a 2 | color: black 3 | -------------------------------------------------------------------------------- /src/cjs.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./index").default; 2 | -------------------------------------------------------------------------------- /test/fixtures/package-json-files/no-postcss/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fixtures/jss/postcss-js/style.js: -------------------------------------------------------------------------------- 1 | module.exports = { a: { color: 'yellow' } } 2 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/empty/fixtures/imports/section.sss: -------------------------------------------------------------------------------- 1 | .import 2 | color: red 3 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/plugins/plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = 'Invalid Plugin'; 2 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/pkg/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc-js/fixtures/imports/section.sss: -------------------------------------------------------------------------------- 1 | .import 2 | color: red 3 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc/fixtures/imports/section.sss: -------------------------------------------------------------------------------- 1 | .import 2 | color: red 3 | -------------------------------------------------------------------------------- /test/fixtures/sss/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.sss' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | bin/* eol=lf 3 | yarn.lock -diff 4 | package-lock.json -diff 5 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/object/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/object/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/css/invalid.config.js: -------------------------------------------------------------------------------- 1 | throw new Error('invalid postcss config'); 2 | -------------------------------------------------------------------------------- /test/fixtures/css/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.css'; 2 | 3 | export default style; 4 | -------------------------------------------------------------------------------- /test/fixtures/less/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.less' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/scss/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.scss' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/empty/fixtures/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: goldenrod; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc-js/fixtures/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc/fixtures/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/array/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: goldenrod; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/jss/exec/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.exec.js'; 2 | 3 | export default style; 4 | -------------------------------------------------------------------------------- /test/fixtures/jss/postcss-js/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.js' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array-mjs/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: goldenrod; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/pkg/index.js: -------------------------------------------------------------------------------- 1 | import style from './index.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/array-mts/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: goldenrod; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/css/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/css/index2.js: -------------------------------------------------------------------------------- 1 | import style from './style2.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/css/style.css: -------------------------------------------------------------------------------- 1 | a { color: black } 2 | 3 | .foo { 4 | float: right; 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /dist 3 | /node_modules 4 | /test/fixtures 5 | CHANGELOG.md 6 | docs/LOADER.md 7 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array-esm-js/imports/section.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: goldenrod; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array/index.js: -------------------------------------------------------------------------------- 1 | import style from './index.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/array/index.js: -------------------------------------------------------------------------------- 1 | import style from './index.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/with-config/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array-mjs/index.js: -------------------------------------------------------------------------------- 1 | import style from './index.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/object/index.js: -------------------------------------------------------------------------------- 1 | import style from './index.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc/fixtures/index.sss: -------------------------------------------------------------------------------- 1 | @import "imports/section.sss" 2 | 3 | .test 4 | color: blue 5 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/array-mts/index.js: -------------------------------------------------------------------------------- 1 | import style from './index.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/object/index.js: -------------------------------------------------------------------------------- 1 | import style from './index.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/css/style2.css: -------------------------------------------------------------------------------- 1 | a { 2 | -x-border-color: blue blue *; 3 | -x-color: * #fafafa; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/empty/fixtures/index.sss: -------------------------------------------------------------------------------- 1 | @import "imports/section.sss" 2 | 3 | .test 4 | color: blue 5 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array-esm-js/index.js: -------------------------------------------------------------------------------- 1 | import style from './index.css' 2 | 3 | export default style 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/pkg/index.css: -------------------------------------------------------------------------------- 1 | @import "imports/section.css"; 2 | 3 | .test { 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc-js/fixtures/index.sss: -------------------------------------------------------------------------------- 1 | @import "imports/section.sss" 2 | 3 | .test 4 | color: blue 5 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array/index.css: -------------------------------------------------------------------------------- 1 | @import 'imports/section.css'; 2 | 3 | .test { 4 | color: cyan; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/object/index.css: -------------------------------------------------------------------------------- 1 | @import "imports/section.css"; 2 | 3 | .test { 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc/expect/index.sss: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red 3 | } 4 | 5 | .test { 6 | color: blue 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc/fixtures/index.css: -------------------------------------------------------------------------------- 1 | @import "imports/section.css"; 2 | 3 | .test { 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/array/index.css: -------------------------------------------------------------------------------- 1 | @import 'imports/section.css'; 2 | 3 | .test { 4 | color: cyan; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/object/index.css: -------------------------------------------------------------------------------- 1 | @import "imports/section.css"; 2 | 3 | .test { 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/empty/expect/index.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | 5 | .test { 6 | color: blue; 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/empty/expect/index.sss: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red 3 | } 4 | 5 | .test { 6 | color: blue 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/empty/fixtures/index.css: -------------------------------------------------------------------------------- 1 | @import "imports/section.css"; 2 | 3 | .test { 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array-esm-js/index.css: -------------------------------------------------------------------------------- 1 | @import 'imports/section.css'; 2 | 3 | .test { 4 | color: cyan; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array-mjs/index.css: -------------------------------------------------------------------------------- 1 | @import 'imports/section.css'; 2 | 3 | .test { 4 | color: cyan; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc-js/expect/index.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | 5 | .test { 6 | color: blue; 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc-js/expect/index.sss: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red 3 | } 4 | 5 | .test { 6 | color: blue 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc-js/fixtures/index.css: -------------------------------------------------------------------------------- 1 | @import "imports/section.css"; 2 | 3 | .test { 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc/expect/index.css: -------------------------------------------------------------------------------- 1 | .import { 2 | color: red; 3 | } 4 | 5 | .test { 6 | color: blue; 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/array-mts/index.css: -------------------------------------------------------------------------------- 1 | @import 'imports/section.css'; 2 | 3 | .test { 4 | color: cyan; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/css/plugins.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-short': { prefix: 'x' }, 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('./config/plugin')() 4 | ] 5 | }; 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/config/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ({ 2 | plugins: [ 3 | require('./plugin')() 4 | ] 5 | }); 6 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/css/custom.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('../config/plugin')() 4 | ] 5 | }; 6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/package-json-files/postcss-v8-in-dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "postcss": "^8.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/package-json-files/postcss-v8-in-devDependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "postcss": "^8.0.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/config/context/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (ctx) => ({ 2 | plugins: [ 3 | require('./plugin')(ctx) 4 | ] 5 | }); 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/options/parser/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | parser: 'postcss-parser' 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/options/syntax/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | syntax: 'postcss-syntax' 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/helpers/getWarnings.js: -------------------------------------------------------------------------------- 1 | import normalizeErrors from "./normalizeErrors"; 2 | 3 | export default (stats) => normalizeErrors(stats.compilation.warnings).sort(); 4 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/options/stringifier/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | stringifier: 'postcss-stringifier' 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/with-config/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | plugins: [['postcss-short', { prefix: 'x' }]], 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/plugins/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | plugins: [ 4 | require('./plugin') 5 | ] 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/plugins/object/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | plugins: { 4 | 'postcss-plugin': {} 5 | } 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/plugins/array/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | plugins: [ 4 | require('postcss-plugin') 5 | ] 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/esparser/index.mjs: -------------------------------------------------------------------------------- 1 | function stringify() { 2 | return ""; 3 | } 4 | 5 | function parse() { 6 | return ""; 7 | } 8 | 9 | export default { stringify, parse }; 10 | -------------------------------------------------------------------------------- /test/helpers/getErrors.js: -------------------------------------------------------------------------------- 1 | import normalizeErrors from "./normalizeErrors"; 2 | 3 | export default (stats, shortError) => 4 | normalizeErrors(stats.compilation.errors, shortError).sort(); 5 | -------------------------------------------------------------------------------- /test/cjs.test.js: -------------------------------------------------------------------------------- 1 | import src from "../src"; 2 | import cjs from "../src/cjs"; 3 | 4 | describe("cjs", () => { 5 | it("should exported", () => { 6 | expect(cjs).toEqual(src); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/plugins/array/options/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | plugins: [ 4 | require('postcss-plugin') 5 | ] 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/css/plugins.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-short': { prefix: 'x' }, 4 | 'postcss-import': {}, 5 | 'postcss-nested': {}, 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "*": [ 3 | "prettier --cache --write --ignore-unknown", 4 | "cspell --cache --no-must-find-files", 5 | ], 6 | "*.js": ["eslint --cache --fix"], 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/err/plugins/object/options/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return { 3 | plugins: { 4 | 'postcss-plugin': { option: true } 5 | } 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "header-max-length": [0], 5 | "body-max-line-length": [0], 6 | "footer-max-line-length": [0], 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "eslint/config"; 2 | import configs from "eslint-config-webpack/configs.js"; 3 | 4 | export default defineConfig([ 5 | { 6 | extends: [configs["recommended-dirty"]], 7 | }, 8 | ]); 9 | -------------------------------------------------------------------------------- /test/fixtures/jss/exec/style.exec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const postcssJS = require('postcss-js'); 4 | 5 | const style = { 6 | a: { 7 | color: 'green' 8 | } 9 | }; 10 | 11 | module.exports = postcssJS.parse(style); 12 | -------------------------------------------------------------------------------- /test/fixtures/esparser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "exports": { 4 | ".": { 5 | "require": "./index.js", 6 | "import": "./index.mjs" 7 | } 8 | }, 9 | "name": "test", 10 | "version": "1.0.0" 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /test/helpers/compile.js: -------------------------------------------------------------------------------- 1 | export default (compiler) => 2 | new Promise((resolve, reject) => { 3 | compiler.run((error, stats) => { 4 | if (error) { 5 | return reject(error); 6 | } 7 | 8 | return resolve(stats); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/helpers/testLoader.js: -------------------------------------------------------------------------------- 1 | function testLoader(content, sourceMap) { 2 | const result = { css: content }; 3 | 4 | if (sourceMap) { 5 | result.map = sourceMap; 6 | } 7 | 8 | return `module.exports = ${JSON.stringify(result)}`; 9 | } 10 | 11 | module.exports = testLoader; 12 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc/.postcssrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "sugarss", 3 | "syntax": "sugarss", 4 | "map": false, 5 | "from": "./test/rc/fixtures/index.css", 6 | "to": "./test/rc/expect/index.css", 7 | "plugins": [ 8 | "postcss-import", 9 | ["postcss-nested", {}] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/plugin/new-api.plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = (opts = {}) => { 2 | return { 3 | postcssPlugin: 'postcss-new-api-plugin', 4 | Declaration: { 5 | width: (node) => { 6 | node.value = '100px'; 7 | }, 8 | }, 9 | }; 10 | }; 11 | module.exports.postcss = true; 12 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/from-to/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | from: '../../css/style.css', 3 | to: '../../css/style.css', 4 | map: { 5 | inline: false, 6 | annotation: false, 7 | sourcesContent: true, 8 | }, 9 | plugins: [ 10 | ['postcss-short', { prefix: 'x' }] 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /test/helpers/readAssets.js: -------------------------------------------------------------------------------- 1 | import readAsset from "./readAsset"; 2 | 3 | export default function readAssets(compiler, stats) { 4 | const assets = {}; 5 | 6 | for (const asset of Object.keys(stats.compilation.assets)) { 7 | assets[asset] = readAsset(asset, compiler, stats); 8 | } 9 | 10 | return assets; 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/rc-js/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "parser": "sugarss", 3 | "syntax": "sugarss", 4 | "map": false, 5 | "from": "./test/rc-js/fixtures/index.css", 6 | "to": "./test/rc-js/expect/index.css", 7 | "plugins": [ 8 | "postcss-import", 9 | ["postcss-nested", {}] 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log 4 | yarn-error.log 5 | npm-debug.log* 6 | .eslintcache 7 | .cspellcache 8 | 9 | dmd 10 | jest 11 | jsdoc-api 12 | 13 | /coverage 14 | /dist 15 | /local 16 | /reports 17 | /node_modules 18 | 19 | .DS_Store 20 | ._* 21 | Thumbs.db 22 | .idea 23 | *.iml 24 | .vscode 25 | *.sublime-project 26 | *.sublime-workspace 27 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-config", 3 | "version": "1.0.0", 4 | "postcss": { 5 | "parser": false, 6 | "syntax": false, 7 | "map": false, 8 | "from": "./index.css", 9 | "to": "./index.css", 10 | "plugins": [ 11 | "postcss-import", 12 | ["postcss-nested", {}] 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const MIN_BABEL_VERSION = 7; 2 | 3 | module.exports = (api) => { 4 | api.assertVersion(MIN_BABEL_VERSION); 5 | api.cache(true); 6 | 7 | return { 8 | presets: [ 9 | [ 10 | "@babel/preset-env", 11 | { 12 | targets: { 13 | node: "18.12.0", 14 | }, 15 | }, 16 | ], 17 | ], 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/config/context/plugin.js: -------------------------------------------------------------------------------- 1 | const postcss = require('postcss'); 2 | 3 | const customPlugin = (ctx) => (css, result) => { 4 | ctx.webpack._compilation.assets['asset.txt'] = { 5 | source () { 6 | return '123' 7 | }, 8 | size () { 9 | return 0 10 | } 11 | } 12 | }; 13 | 14 | module.exports = postcss.plugin('plugin', customPlugin); 15 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/with-config/style.css: -------------------------------------------------------------------------------- 1 | a { color: black } 2 | 3 | .foo { 4 | float: right; 5 | } 6 | 7 | .bar { 8 | -x-border-color: blue blue *; 9 | -x-color: * #fafafa; 10 | } 11 | 12 | @media (prefers-color-scheme: dark) { 13 | :root { /* :root is for HTML documents */ 14 | --text-color: white 15 | } 16 | body { 17 | background: black 18 | } 19 | } -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: "Dependency Review" 2 | on: [pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | dependency-review: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: "Checkout Repository" 12 | uses: actions/checkout@v5 13 | - name: "Dependency Review" 14 | uses: actions/dependency-review-action@v4 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | testTimeout: 10000, 4 | transform: { 5 | "\\.[mc]?tsx?$": ["ts-jest", { rootDir: ".", esModuleInterop: true }], 6 | "\\.[mc]?jsx?$": "babel-jest", 7 | }, 8 | // ignore node_modules except for strip-ansi and ansi-regex 9 | transformIgnorePatterns: ["node_modules/(?!strip-ansi|ansi-regex)"], 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/all-options/postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = () => ({ 4 | from: path.resolve(__dirname, '../../sss/style.sss'), 5 | to: path.resolve(__dirname, '../../sss/style.css'), 6 | parser: 'sugarss', 7 | map: { 8 | inline: true, 9 | annotation: true, 10 | }, 11 | plugins: [ 12 | ['postcss-short', { prefix: 'x' }] 13 | ] 14 | }); 15 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/config/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const postcss = require('postcss'); 4 | 5 | module.exports = postcss.plugin('plugin', (options) => { 6 | options = Object.assign({ alpha: '1.0' }, options); 7 | 8 | return (css, result) => { 9 | css.walkDecls((decl) => { 10 | if (decl.value === 'black') { 11 | decl.value = 'rgba(0, 0, 0, ' + options.alpha + ')' 12 | } 13 | }) 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /test/fixtures/plugin/other-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const postcss = require('postcss'); 4 | 5 | module.exports = postcss.plugin('my-plugin', (options) => { 6 | options = Object.assign({ alpha: '1.0' }, options); 7 | 8 | return (root, result) => { 9 | root.walkDecls((decl) => { 10 | if (decl.value === 'blue') { 11 | decl.value = 'rgba(0, 0, 255, ' + options.alpha + ')' 12 | } 13 | }) 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /test/fixtures/plugin/plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const postcss = require('postcss'); 4 | 5 | module.exports = postcss.plugin('my-plugin', (options) => { 6 | const myOptions = {...{ alpha: '1.0', color: 'black' }, ...options}; 7 | 8 | return (root, result) => { 9 | root.walkDecls((decl) => { 10 | if (decl.value === myOptions.color) { 11 | decl.value = 'rgba(0, 0, 0, ' + myOptions.alpha + ')' 12 | } 13 | }) 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/object/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | return { 3 | parser: 'sugarss', 4 | syntax: 'sugarss', 5 | map: api.mode === 'development' ? 'inline' : false, 6 | from: './test/fixtures/config-autoload/js/object/index.css', 7 | to: './test/fixtures/config-autoload/js/object/expect/index.css', 8 | plugins: { 9 | 'postcss-import': {}, 10 | 'postcss-nested': {}, 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /test/helpers/getExecutedCode.js: -------------------------------------------------------------------------------- 1 | import { execute, readAsset } from "./index"; 2 | 3 | export default (asset, compiler, stats) => { 4 | let executed = execute(readAsset(asset, compiler, stats)); 5 | 6 | if (Array.isArray(executed)) { 7 | executed = executed.map((module) => { 8 | // Todo remove after drop webpack@4 9 | 10 | module[0] = module[0].replaceAll(/\?.*!/g, "?[ident]!"); 11 | 12 | return module; 13 | }); 14 | } 15 | 16 | return executed; 17 | }; 18 | -------------------------------------------------------------------------------- /test/fixtures/plugin/default-other-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const postcss = require('postcss'); 4 | 5 | module.exports = { 6 | default: postcss.plugin('my-plugin', (options) => { 7 | options = { alpha: '1.0', color: 'blue', ...options }; 8 | 9 | return (root, result) => { 10 | root.walkDecls((decl) => { 11 | if (decl.value === options.color) { 12 | decl.value = 'rgba(0, 0, 255, ' + options.alpha + ')' 13 | } 14 | }) 15 | } 16 | }), 17 | }; 18 | -------------------------------------------------------------------------------- /test/helpers/execute.js: -------------------------------------------------------------------------------- 1 | import Module from "node:module"; 2 | import path from "node:path"; 3 | 4 | const parentModule = module; 5 | 6 | export default (code) => { 7 | const resource = "test.js"; 8 | const module = new Module(resource, parentModule); 9 | 10 | module.paths = Module._nodeModulePaths( 11 | path.resolve(__dirname, "../fixtures"), 12 | ); 13 | module.filename = resource; 14 | 15 | module._compile( 16 | `let __export__;${code};module.exports = __export__;`, 17 | resource, 18 | ); 19 | 20 | return module.exports; 21 | }; 22 | -------------------------------------------------------------------------------- /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en,en-gb", 4 | "words": [ 5 | "stringifier", 6 | "sugarss", 7 | "unresolve", 8 | "esparser", 9 | "memfs", 10 | "postcssrc", 11 | "XSSI", 12 | "klona", 13 | "hspace", 14 | "vspace", 15 | "commitlint", 16 | "jiti" 17 | ], 18 | 19 | "ignorePaths": [ 20 | "CHANGELOG.md", 21 | "package.json", 22 | "dist/**", 23 | "**/__snapshots__/**", 24 | "package-lock.json", 25 | "node_modules", 26 | "coverage", 27 | "*.log" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /test/helpers/getCodeFromBundle.js: -------------------------------------------------------------------------------- 1 | export default (id, stats) => { 2 | const { modules } = stats.compilation; 3 | const module = modules.find((m) => m.id.endsWith(id)); 4 | const { _source } = module; 5 | 6 | const code = (_source._value || _source._valueAsString).replace( 7 | "module.exports = ", 8 | "", 9 | ); 10 | 11 | let result; 12 | 13 | try { 14 | result = JSON.parse(code); 15 | } catch { 16 | result = { css: code }; 17 | } 18 | 19 | const { css, map: sourceMap } = result; 20 | 21 | return { css, sourceMap }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/config.d.ts: -------------------------------------------------------------------------------- 1 | import type { Config as PostCSSConfig } from "postcss-load-config"; 2 | import type { LoaderContext } from "webpack"; 3 | 4 | type PostCSSLoaderContext = LoaderContext; 5 | 6 | interface PostCSSLoaderAPI { 7 | mode: PostCSSLoaderContext["mode"]; 8 | file: PostCSSLoaderContext["resourcePath"]; 9 | webpackLoaderContext: PostCSSLoaderContext; 10 | env: PostCSSLoaderContext["mode"]; 11 | options: PostCSSConfig; 12 | } 13 | 14 | export type PostCSSLoaderOptions = 15 | | PostCSSConfig 16 | | ((api: PostCSSLoaderAPI) => PostCSSConfig); 17 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | export { default as readsAssets } from "./readAssets"; 2 | export { default as compile } from "./compile"; 3 | export { default as normalizeErrors } from "./normalizeErrors"; 4 | export { default as execute } from "./execute"; 5 | export { default as getCompiler } from "./getCompiler"; 6 | export { default as getCodeFromBundle } from "./getCodeFromBundle"; 7 | export { default as getExecutedCode } from "./getExecutedCode"; 8 | export { default as getErrors } from "./getErrors"; 9 | export { default as readAsset } from "./readAsset"; 10 | export { default as getWarnings } from "./getWarnings"; 11 | -------------------------------------------------------------------------------- /test/fixtures/css/style.css: -------------------------------------------------------------------------------- 1 | a { 2 | color: black; 3 | } 4 | 5 | a { 6 | color: red; 7 | } 8 | 9 | a { 10 | color: green; 11 | } 12 | 13 | a { 14 | color: blue; 15 | } 16 | 17 | .class { 18 | -x-border-color: blue blue *; 19 | -x-color: * #fafafa; 20 | } 21 | 22 | .class-foo { 23 | -z-border-color: blue blue *; 24 | -z-color: * #fafafa; 25 | } 26 | 27 | .phone { 28 | &_title { 29 | width: 500px; 30 | 31 | @media (max-width: 500px) { 32 | width: auto; 33 | } 34 | 35 | body.is_dark & { 36 | color: white; 37 | } 38 | } 39 | 40 | img { 41 | display: block; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | return { 3 | parser: 'sugarss', 4 | syntax: 'sugarss', 5 | map: api.mode === 'development' ? 'inline' : false, 6 | from: './test/fixtures/config-autoload/js/object/index.css', 7 | to: './test/fixtures/config-autoload/js/object/expect/index.css', 8 | plugins: [ 9 | 'postcss-import', 10 | [ 11 | 'postcss-nested', 12 | { 13 | // Options 14 | } 15 | ], 16 | require('postcss-nested'), 17 | require('postcss-nested')({ /* Options */ }), 18 | ] 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /test/helpers/readAsset.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | export default (asset, compiler, stats) => { 4 | const usedFs = compiler.outputFileSystem; 5 | const outputPath = stats.compilation.outputOptions.path; 6 | 7 | let data = ""; 8 | let targetFile = asset; 9 | 10 | const queryStringIdx = targetFile.indexOf("?"); 11 | 12 | if (queryStringIdx >= 0) { 13 | targetFile = targetFile.slice(0, queryStringIdx); 14 | } 15 | 16 | try { 17 | data = usedFs.readFileSync(path.join(outputPath, targetFile)).toString(); 18 | } catch (error) { 19 | data = error.toString(); 20 | } 21 | 22 | return data; 23 | }; 24 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array-mjs/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | import postcssNested from 'postcss-nested'; 2 | export default function (api) { 3 | return { 4 | parser: 'sugarss', 5 | syntax: 'sugarss', 6 | map: api.mode === 'development' ? 'inline' : false, 7 | from: './test/fixtures/config-autoload/js/object/index.css', 8 | to: './test/fixtures/config-autoload/js/object/expect/index.css', 9 | plugins: [ 10 | 'postcss-import', 11 | [ 12 | 'postcss-nested', 13 | { 14 | // Options 15 | } 16 | ], 17 | postcssNested, 18 | postcssNested({ /* Options */ }), 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/js/array-esm-js/postcss.config.js: -------------------------------------------------------------------------------- 1 | import postcssNested from 'postcss-nested'; 2 | export default function (api) { 3 | return { 4 | parser: 'sugarss', 5 | syntax: 'sugarss', 6 | map: api.mode === 'development' ? 'inline' : false, 7 | from: './test/fixtures/config-autoload/js/object/index.css', 8 | to: './test/fixtures/config-autoload/js/object/expect/index.css', 9 | plugins: [ 10 | 'postcss-import', 11 | [ 12 | 'postcss-nested', 13 | { 14 | // Options 15 | } 16 | ], 17 | postcssNested, 18 | postcssNested({ /* Options */ }), 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /test/fixtures/config-scope/api/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | if (!api.mode) { 3 | throw new Error(`Failed, no ${api.mode} API`); 4 | } 5 | 6 | if (!api.file) { 7 | throw new Error(`Failed, no ${api.file} API`); 8 | } 9 | 10 | if (!api.webpackLoaderContext) { 11 | throw new Error(`Failed, no ${api.webpackLoaderContext} API`); 12 | } 13 | 14 | if (!api.env) { 15 | throw new Error(`Failed, no ${api.env} API`); 16 | } 17 | 18 | if (!api.options) { 19 | throw new Error(`Failed, no ${api.options} API`); 20 | } 21 | 22 | return { 23 | plugins: [['postcss-short', { prefix: 'x' }]], 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /test/helpers/normalizeErrors.js: -------------------------------------------------------------------------------- 1 | import stripAnsi from "strip-ansi"; 2 | 3 | function removeCWD(str) { 4 | const isWin = process.platform === "win32"; 5 | let cwd = process.cwd(); 6 | 7 | if (isWin) { 8 | str = str.replaceAll("\\", "/"); 9 | 10 | cwd = cwd.replaceAll("\\", "/"); 11 | } 12 | 13 | return stripAnsi(str) 14 | .replace(/\(from .*?\)/, "(from `replaced original path`)") 15 | .replaceAll(new RegExp(cwd, "g"), ""); 16 | } 17 | 18 | export default (errors, shortError) => 19 | errors.map((error) => { 20 | let errorMessage = error.toString(); 21 | 22 | if (shortError) { 23 | errorMessage = errorMessage.split("\n").slice(0, 2).join("\n"); 24 | } 25 | 26 | return removeCWD(errorMessage.split("\n").slice(0, 12).join("\n")); 27 | }); 28 | -------------------------------------------------------------------------------- /test/__snapshots__/execute.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"execute" option should reuse PostCSS AST with JS styles: css 1`] = ` 4 | "a { 5 | color: green 6 | }" 7 | `; 8 | 9 | exports[`"execute" option should reuse PostCSS AST with JS styles: errors 1`] = `[]`; 10 | 11 | exports[`"execute" option should reuse PostCSS AST with JS styles: warnings 1`] = `[]`; 12 | 13 | exports[`"execute" option should work with "Boolean" value: css 1`] = ` 14 | "a { 15 | color: green 16 | }" 17 | `; 18 | 19 | exports[`"execute" option should work with "Boolean" value: errors 1`] = `[]`; 20 | 21 | exports[`"execute" option should work with "Boolean" value: warnings 1`] = `[]`; 22 | 23 | exports[`"execute" option should work with "postcss-js" parser: css 1`] = ` 24 | "a { 25 | color: yellow 26 | }" 27 | `; 28 | 29 | exports[`"execute" option should work with "postcss-js" parser: errors 1`] = `[]`; 30 | 31 | exports[`"execute" option should work with "postcss-js" parser: warnings 1`] = `[]`; 32 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/object/postcss.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config as PostCSSConfig } from 'postcss-load-config'; 2 | import type { LoaderContext } from 'webpack'; 3 | 4 | type PostCSSLoaderContext = LoaderContext; 5 | 6 | interface PostCSSLoaderAPI { 7 | mode: PostCSSLoaderContext['mode']; 8 | file: PostCSSLoaderContext['resourcePath']; 9 | webpackLoaderContext: PostCSSLoaderContext; 10 | env: PostCSSLoaderContext['mode']; 11 | options: PostCSSConfig; 12 | } 13 | 14 | type PostCSSLoaderOptions = PostCSSConfig | ((api: PostCSSLoaderAPI) => PostCSSConfig); 15 | 16 | const config: PostCSSLoaderOptions = function (api) { 17 | return { 18 | parser: 'sugarss', 19 | syntax: 'sugarss', 20 | map: api.mode === 'development' ? 'inline' : false, 21 | from: './test/fixtures/config-autoload/ts/object/index.css', 22 | to: './test/fixtures/config-autoload/ts/object/expect/index.css', 23 | plugins: { 24 | 'postcss-import': {}, 25 | 'postcss-nested': {}, 26 | } 27 | } 28 | }; 29 | 30 | export default config; 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/helpers/astLoader.js: -------------------------------------------------------------------------------- 1 | import Module from "node:module"; 2 | 3 | const postcss = require("postcss"); 4 | 5 | const parentModule = module; 6 | 7 | function exec(code, loaderContext) { 8 | const { resource, context } = loaderContext; 9 | 10 | const module = new Module(resource, parentModule); 11 | 12 | module.paths = Module._nodeModulePaths(context); 13 | module.filename = resource; 14 | 15 | module._compile(code, resource); 16 | 17 | return module.exports; 18 | } 19 | 20 | module.exports = function astLoader(content) { 21 | const callback = this.async(); 22 | const { spy = globalThis.jest.fn(), execute } = this.query; 23 | 24 | if (execute) { 25 | content = exec(content, this); 26 | } 27 | 28 | postcss() 29 | .process(content) 30 | .then((result) => { 31 | const ast = { 32 | type: "postcss", 33 | version: result.processor.version, 34 | root: result.root, 35 | }; 36 | 37 | Object.defineProperty(ast, "root", { 38 | get: spy.mockReturnValue(result.root), 39 | }); 40 | 41 | callback(null, result.css, result.map, { ast }); 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/array-mts/postcss.config.mts: -------------------------------------------------------------------------------- 1 | import type { Config as PostCSSConfig } from 'postcss-load-config'; 2 | import type { LoaderContext } from 'webpack'; 3 | 4 | type PostCSSLoaderContext = LoaderContext; 5 | 6 | interface PostCSSLoaderAPI { 7 | mode: PostCSSLoaderContext['mode']; 8 | file: PostCSSLoaderContext['resourcePath']; 9 | webpackLoaderContext: PostCSSLoaderContext; 10 | env: PostCSSLoaderContext['mode']; 11 | options: PostCSSConfig; 12 | } 13 | 14 | type PostCSSLoaderOptions = PostCSSConfig | ((api: PostCSSLoaderAPI) => PostCSSConfig); 15 | 16 | const config: PostCSSLoaderOptions = function (api) { 17 | return { 18 | parser: 'sugarss', 19 | syntax: 'sugarss', 20 | map: api.mode === 'development' ? 'inline' : false, 21 | from: './test/fixtures/config-autoload/ts/object/index.css', 22 | to: './test/fixtures/config-autoload/ts/object/expect/index.css', 23 | plugins: [ 24 | 'postcss-import', 25 | [ 26 | 'postcss-nested', 27 | { 28 | // Options 29 | } 30 | ], 31 | require('postcss-nested'), 32 | require('postcss-nested')({ /* Options */ }), 33 | ] 34 | } 35 | }; 36 | 37 | export default config; 38 | -------------------------------------------------------------------------------- /test/fixtures/config-autoload/ts/array/postcss.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config as PostCSSConfig } from 'postcss-load-config'; 2 | import type { LoaderContext } from 'webpack'; 3 | 4 | type PostCSSLoaderContext = LoaderContext; 5 | 6 | interface PostCSSLoaderAPI { 7 | mode: PostCSSLoaderContext['mode']; 8 | file: PostCSSLoaderContext['resourcePath']; 9 | webpackLoaderContext: PostCSSLoaderContext; 10 | env: PostCSSLoaderContext['mode']; 11 | options: PostCSSConfig; 12 | } 13 | 14 | type PostCSSLoaderOptions = PostCSSConfig | ((api: PostCSSLoaderAPI) => PostCSSConfig); 15 | 16 | const config: PostCSSLoaderOptions = function (api) { 17 | return { 18 | parser: 'sugarss', 19 | syntax: 'sugarss', 20 | map: api.mode === 'development' ? 'inline' : false, 21 | from: './test/fixtures/config-autoload/ts/object/index.css', 22 | to: './test/fixtures/config-autoload/ts/object/expect/index.css', 23 | plugins: [ 24 | 'postcss-import', 25 | [ 26 | 'postcss-nested', 27 | { 28 | // Options 29 | } 30 | ], 31 | require('postcss-nested'), 32 | require('postcss-nested')({ /* Options */ }), 33 | ] 34 | } 35 | }; 36 | 37 | module.exports = config; 38 | -------------------------------------------------------------------------------- /test/fixtures/esparser/runManual.mjs: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import path from "path"; 3 | 4 | const __dirname = path.resolve(); 5 | const rootDir = path.resolve(__dirname, "test/helpers"); 6 | 7 | const compiler = webpack({ 8 | target: 'node', 9 | mode: "development", 10 | devtool: false, 11 | context: path.resolve(rootDir, "../fixtures"), 12 | entry: path.resolve(rootDir, "../fixtures", "./sss/index.js"), 13 | output: { 14 | path: path.resolve(rootDir, "../outputs"), 15 | filename: "[name].bundle.js", 16 | chunkFilename: "[name].chunk.js", 17 | publicPath: "/webpack/public/path/", 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(css|sss)$/i, 23 | use: [ 24 | 'css-loader', 25 | { 26 | loader: path.resolve(rootDir, "../../dist"), 27 | options: { 28 | postcssOptions: { 29 | parser: path.resolve(rootDir, "../fixtures/esparser/index.mjs"), 30 | stringifier: path.resolve(rootDir, "../fixtures/esparser/index.mjs"), 31 | syntax: path.resolve(rootDir, "../fixtures/esparser/index.mjs"), 32 | }, 33 | }, 34 | }, 35 | ], 36 | }, 37 | ], 38 | }, 39 | plugins: [], 40 | }); 41 | 42 | compiler.run((error) => { 43 | if (error) { 44 | throw error; 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /test/helpers/getCompiler.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import { Volume, createFsFromVolume } from "memfs"; 4 | import webpack from "webpack"; 5 | 6 | export default (fixture, loaderOptions = {}, config = {}) => { 7 | const fullConfig = { 8 | mode: "development", 9 | devtool: config.devtool || false, 10 | context: path.resolve(__dirname, "../fixtures"), 11 | entry: path.resolve(__dirname, "../fixtures", fixture), 12 | output: { 13 | path: path.resolve(__dirname, "../outputs"), 14 | filename: "[name].bundle.js", 15 | chunkFilename: "[name].chunk.js", 16 | publicPath: "/webpack/public/path/", 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.(css|sss)$/i, 22 | use: [ 23 | { 24 | loader: require.resolve("./testLoader"), 25 | options: {}, 26 | }, 27 | { 28 | loader: path.resolve(__dirname, "../../src"), 29 | options: loaderOptions || {}, 30 | }, 31 | ], 32 | }, 33 | ], 34 | }, 35 | plugins: [], 36 | ...config, 37 | }; 38 | 39 | const compiler = webpack(fullConfig); 40 | 41 | if (!config.outputFileSystem) { 42 | const outputFileSystem = createFsFromVolume(new Volume()); 43 | // Todo remove when we drop webpack@4 support 44 | outputFileSystem.join = path.join.bind(path); 45 | 46 | compiler.outputFileSystem = outputFileSystem; 47 | } 48 | 49 | return compiler; 50 | }; 51 | -------------------------------------------------------------------------------- /test/implementation.test.js: -------------------------------------------------------------------------------- 1 | import postcss from "postcss"; 2 | 3 | import { 4 | compile, 5 | getCodeFromBundle, 6 | getCompiler, 7 | getErrors, 8 | getWarnings, 9 | } from "./helpers"; 10 | 11 | describe('"implementation" option', () => { 12 | it("should work with implementation is string", async () => { 13 | const compiler = getCompiler("./css/index.js", { 14 | implementation: require.resolve("postcss"), 15 | }); 16 | const stats = await compile(compiler); 17 | const codeFromBundle = getCodeFromBundle("style.css", stats); 18 | 19 | expect(codeFromBundle.css).toMatchSnapshot("css"); 20 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 21 | expect(getErrors(stats)).toMatchSnapshot("errors"); 22 | }); 23 | 24 | it("should throw error when unresolved package", async () => { 25 | const compiler = getCompiler("./css/index.js", { 26 | implementation: "unresolved", 27 | }); 28 | const stats = await compile(compiler); 29 | 30 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 31 | expect(getErrors(stats, true)).toMatchSnapshot("errors"); 32 | }); 33 | 34 | it("should work with a custom instance of PostCSS", async () => { 35 | const spy = jest.fn(postcss); 36 | const compiler = getCompiler("./css/index.js", { 37 | // Wrap the spy so it is an instanceof Function 38 | implementation: (...args) => spy(...args), 39 | }); 40 | const stats = await compile(compiler); 41 | const codeFromBundle = getCodeFromBundle("style.css", stats); 42 | 43 | expect(spy).toHaveBeenCalledTimes(1); 44 | expect(codeFromBundle.css).toMatchSnapshot("css"); 45 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 46 | expect(getErrors(stats)).toMatchSnapshot("errors"); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "PostCSS Loader options", 3 | "type": "object", 4 | "properties": { 5 | "postcssOptions": { 6 | "description": "Options to pass through to `Postcss`.", 7 | "link": "https://github.com/webpack/postcss-loader#postcssOptions", 8 | "anyOf": [ 9 | { 10 | "type": "object", 11 | "additionalProperties": true, 12 | "properties": { 13 | "config": { 14 | "description": "Allows to specify PostCSS config path.", 15 | "link": "https://github.com/webpack/postcss-loader#config", 16 | "anyOf": [ 17 | { 18 | "description": "Allows to specify the path to the configuration file", 19 | "type": "string" 20 | }, 21 | { 22 | "description": "Enables/Disables autoloading config", 23 | "type": "boolean" 24 | } 25 | ] 26 | } 27 | } 28 | }, 29 | { 30 | "instanceof": "Function" 31 | } 32 | ] 33 | }, 34 | "execute": { 35 | "description": "Enables/Disables PostCSS parser support in 'CSS-in-JS'.", 36 | "link": "https://github.com/webpack/postcss-loader#execute", 37 | "type": "boolean" 38 | }, 39 | "sourceMap": { 40 | "description": "Enables/Disables generation of source maps.", 41 | "link": "https://github.com/webpack/postcss-loader#sourcemap", 42 | "type": "boolean" 43 | }, 44 | "implementation": { 45 | "description": "The implementation of postcss to use, instead of the locally installed version", 46 | "link": "https://github.com/webpack/postcss-loader#implementation", 47 | "anyOf": [ 48 | { 49 | "type": "string" 50 | }, 51 | { 52 | "instanceof": "Function" 53 | } 54 | ] 55 | } 56 | }, 57 | "additionalProperties": false 58 | } 59 | -------------------------------------------------------------------------------- /test/__snapshots__/implementation.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"implementation" option should throw error when unresolved package: errors 1`] = ` 4 | [ 5 | "ModuleBuildError: Module build failed (from \`replaced original path\`): 6 | NonErrorEmittedError: (Emitted value instead of an instance of Error) Error: Cannot find module 'unresolved' from 'src/utils.js'", 7 | ] 8 | `; 9 | 10 | exports[`"implementation" option should throw error when unresolved package: warnings 1`] = `[]`; 11 | 12 | exports[`"implementation" option should work with a custom instance of PostCSS: css 1`] = ` 13 | "a { 14 | color: black; 15 | } 16 | 17 | a { 18 | color: red; 19 | } 20 | 21 | a { 22 | color: green; 23 | } 24 | 25 | a { 26 | color: blue; 27 | } 28 | 29 | .class { 30 | -x-border-color: blue blue *; 31 | -x-color: * #fafafa; 32 | } 33 | 34 | .class-foo { 35 | -z-border-color: blue blue *; 36 | -z-color: * #fafafa; 37 | } 38 | 39 | .phone { 40 | &_title { 41 | width: 500px; 42 | 43 | @media (max-width: 500px) { 44 | width: auto; 45 | } 46 | 47 | body.is_dark & { 48 | color: white; 49 | } 50 | } 51 | 52 | img { 53 | display: block; 54 | } 55 | } 56 | " 57 | `; 58 | 59 | exports[`"implementation" option should work with a custom instance of PostCSS: errors 1`] = `[]`; 60 | 61 | exports[`"implementation" option should work with a custom instance of PostCSS: warnings 1`] = `[]`; 62 | 63 | exports[`"implementation" option should work with implementation is string: css 1`] = ` 64 | "a { 65 | color: black; 66 | } 67 | 68 | a { 69 | color: red; 70 | } 71 | 72 | a { 73 | color: green; 74 | } 75 | 76 | a { 77 | color: blue; 78 | } 79 | 80 | .class { 81 | -x-border-color: blue blue *; 82 | -x-color: * #fafafa; 83 | } 84 | 85 | .class-foo { 86 | -z-border-color: blue blue *; 87 | -z-color: * #fafafa; 88 | } 89 | 90 | .phone { 91 | &_title { 92 | width: 500px; 93 | 94 | @media (max-width: 500px) { 95 | width: auto; 96 | } 97 | 98 | body.is_dark & { 99 | color: white; 100 | } 101 | } 102 | 103 | img { 104 | display: block; 105 | } 106 | } 107 | " 108 | `; 109 | 110 | exports[`"implementation" option should work with implementation is string: errors 1`] = `[]`; 111 | 112 | exports[`"implementation" option should work with implementation is string: warnings 1`] = `[]`; 113 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: postcss-loader 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | pull_request: 9 | branches: 10 | - main 11 | - next 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | lint: 18 | name: Lint - ${{ matrix.os }} - Node v${{ matrix.node-version }} 19 | 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | strategy: 24 | matrix: 25 | os: [ubuntu-latest] 26 | node-version: [lts/*] 27 | 28 | runs-on: ${{ matrix.os }} 29 | 30 | concurrency: 31 | group: lint-${{ matrix.os }}-v${{ matrix.node-version }}-${{ github.ref }} 32 | cancel-in-progress: true 33 | 34 | steps: 35 | - uses: actions/checkout@v5 36 | with: 37 | fetch-depth: 0 38 | 39 | - name: Use Node.js ${{ matrix.node-version }} 40 | uses: actions/setup-node@v4 41 | with: 42 | node-version: ${{ matrix.node-version }} 43 | cache: "npm" 44 | 45 | - name: Install dependencies 46 | run: npm ci 47 | 48 | - name: Lint 49 | run: npm run lint 50 | 51 | - name: Security audit 52 | run: npm run security 53 | 54 | - name: Validate PR commits with commitlint 55 | if: github.event_name == 'pull_request' 56 | run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose 57 | 58 | test: 59 | name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }} 60 | 61 | strategy: 62 | matrix: 63 | os: [ubuntu-latest, windows-latest, macos-latest] 64 | node-version: [18.x, 20.x, 22.x, 24.x] 65 | webpack-version: [latest] 66 | 67 | runs-on: ${{ matrix.os }} 68 | 69 | concurrency: 70 | group: test-${{ matrix.os }}-v${{ matrix.node-version }}-${{ matrix.webpack-version }}-${{ github.ref }} 71 | cancel-in-progress: true 72 | 73 | steps: 74 | - name: Setup Git 75 | if: matrix.os == 'windows-latest' 76 | run: git config --global core.autocrlf input 77 | 78 | - uses: actions/checkout@v5 79 | 80 | - name: Use Node.js ${{ matrix.node-version }} 81 | uses: actions/setup-node@v4 82 | with: 83 | node-version: ${{ matrix.node-version }} 84 | cache: "npm" 85 | 86 | - name: Install dependencies 87 | run: npm ci 88 | 89 | - name: Install webpack ${{ matrix.webpack-version }} 90 | if: matrix.webpack-version != 'latest' 91 | run: npm i webpack@${{ matrix.webpack-version }} 92 | 93 | - name: Run tests for webpack version ${{ matrix.webpack-version }} 94 | run: npm run test:coverage -- --ci 95 | 96 | - name: Submit coverage data to codecov 97 | uses: codecov/codecov-action@v5 98 | with: 99 | token: ${{ secrets.CODECOV_TOKEN }} 100 | -------------------------------------------------------------------------------- /test/execute.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import { 4 | compile, 5 | getCodeFromBundle, 6 | getCompiler, 7 | getErrors, 8 | getWarnings, 9 | } from "./helpers"; 10 | 11 | describe('"execute" option', () => { 12 | it('should work with "Boolean" value', async () => { 13 | const compiler = getCompiler( 14 | "./jss/exec/index.js", 15 | {}, 16 | { 17 | module: { 18 | rules: [ 19 | { 20 | test: /style\.(exec\.js|js)$/i, 21 | use: [ 22 | { 23 | loader: require.resolve("./helpers/testLoader"), 24 | options: {}, 25 | }, 26 | { 27 | loader: path.resolve(__dirname, "../src"), 28 | options: { 29 | execute: true, 30 | }, 31 | }, 32 | ], 33 | }, 34 | ], 35 | }, 36 | }, 37 | ); 38 | const stats = await compile(compiler); 39 | const codeFromBundle = getCodeFromBundle("style.exec.js", stats); 40 | 41 | expect(codeFromBundle.css).toMatchSnapshot("css"); 42 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 43 | expect(getErrors(stats)).toMatchSnapshot("errors"); 44 | }); 45 | 46 | it('should work with "postcss-js" parser', async () => { 47 | const compiler = getCompiler( 48 | "./jss/postcss-js/index.js", 49 | {}, 50 | { 51 | module: { 52 | rules: [ 53 | { 54 | test: /style\.js$/i, 55 | use: [ 56 | { 57 | loader: require.resolve("./helpers/testLoader"), 58 | options: {}, 59 | }, 60 | { 61 | loader: path.resolve(__dirname, "../src"), 62 | options: { 63 | postcssOptions: { 64 | parser: "postcss-js", 65 | }, 66 | execute: true, 67 | }, 68 | }, 69 | ], 70 | }, 71 | ], 72 | }, 73 | }, 74 | ); 75 | const stats = await compile(compiler); 76 | const codeFromBundle = getCodeFromBundle("style.js", stats); 77 | 78 | expect(codeFromBundle.css).toMatchSnapshot("css"); 79 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 80 | expect(getErrors(stats)).toMatchSnapshot("errors"); 81 | }); 82 | 83 | it("should reuse PostCSS AST with JS styles", async () => { 84 | const spy = jest.fn(); 85 | const compiler = getCompiler( 86 | "./jss/exec/index.js", 87 | {}, 88 | { 89 | module: { 90 | rules: [ 91 | { 92 | test: /style\.(exec\.js|js)$/i, 93 | use: [ 94 | { 95 | loader: require.resolve("./helpers/testLoader"), 96 | options: {}, 97 | }, 98 | { 99 | loader: path.resolve(__dirname, "../src"), 100 | options: { 101 | execute: true, 102 | }, 103 | }, 104 | { 105 | loader: require.resolve("./helpers/astLoader"), 106 | options: { spy, execute: true }, 107 | }, 108 | ], 109 | }, 110 | ], 111 | }, 112 | }, 113 | ); 114 | const stats = await compile(compiler); 115 | const codeFromBundle = getCodeFromBundle("style.exec.js", stats); 116 | 117 | expect(spy).toHaveBeenCalledTimes(1); 118 | expect(codeFromBundle.css).toMatchSnapshot("css"); 119 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 120 | expect(getErrors(stats)).toMatchSnapshot("errors"); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-loader", 3 | "version": "8.2.0", 4 | "description": "PostCSS loader for webpack", 5 | "keywords": [ 6 | "css", 7 | "postcss", 8 | "postcss-runner", 9 | "webpack", 10 | "webpack-loader" 11 | ], 12 | "homepage": "https://github.com/webpack/postcss-loader", 13 | "bugs": "https://github.com/webpack/postcss-loader/issues", 14 | "repository": "webpack/postcss-loader", 15 | "funding": { 16 | "type": "opencollective", 17 | "url": "https://opencollective.com/webpack" 18 | }, 19 | "license": "MIT", 20 | "author": "Andrey Sitnik ", 21 | "main": "dist/cjs.js", 22 | "files": [ 23 | "dist" 24 | ], 25 | "scripts": { 26 | "start": "npm run build -- -w", 27 | "clean": "del-cli dist", 28 | "prebuild": "npm run clean", 29 | "build": "cross-env NODE_ENV=production babel src -d dist --copy-files", 30 | "commitlint": "commitlint --from=main", 31 | "security": "npm audit --production", 32 | "lint:prettier": "prettier --cache --list-different .", 33 | "lint:js": "eslint --cache .", 34 | "lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"", 35 | "lint": "npm-run-all -l -p \"lint:**\"", 36 | "fix:js": "npm run lint:js -- --fix", 37 | "fix:prettier": "npm run lint:prettier -- --write", 38 | "fix": "npm-run-all -l fix:js fix:prettier", 39 | "test:only": "cross-env NODE_ENV=test jest", 40 | "test:watch": "npm run test:only -- --watch", 41 | "test:coverage": "npm run test:only -- --collectCoverageFrom=\"src/**/*.js\" --coverage", 42 | "pretest": "npm run lint", 43 | "test": "npm run test:coverage", 44 | "prepare": "husky && npm run build", 45 | "release": "standard-version" 46 | }, 47 | "dependencies": { 48 | "cosmiconfig": "^9.0.0", 49 | "jiti": "^2.5.1", 50 | "semver": "^7.6.2" 51 | }, 52 | "devDependencies": { 53 | "@babel/cli": "^7.24.7", 54 | "@babel/core": "^7.24.7", 55 | "@babel/preset-env": "^7.24.7", 56 | "@commitlint/cli": "^18.6.1", 57 | "@commitlint/config-conventional": "^18.6.2", 58 | "@eslint/js": "^9.32.0", 59 | "@eslint/markdown": "^7.1.0", 60 | "@stylistic/eslint-plugin": "^5.2.2", 61 | "babel-jest": "^30.0.5", 62 | "cross-env": "^7.0.3", 63 | "cspell": "^8.10.0", 64 | "cssnano": "^6.1.2", 65 | "del": "^7.1.0", 66 | "del-cli": "^5.1.0", 67 | "eslint": "^9.32.0", 68 | "eslint-config-prettier": "^10.1.8", 69 | "eslint-config-webpack": "^4.5.1", 70 | "eslint-plugin-import": "^2.32.0", 71 | "eslint-plugin-jest": "^29.0.1", 72 | "eslint-plugin-n": "^17.21.2", 73 | "eslint-plugin-prettier": "^5.5.3", 74 | "eslint-plugin-unicorn": "^60.0.0", 75 | "globals": "^16.3.0", 76 | "husky": "^9.1.3", 77 | "jest": "^30.0.5", 78 | "less": "^4.2.0", 79 | "less-loader": "^12.2.0", 80 | "lint-staged": "^15.2.7", 81 | "memfs": "^4.9.3", 82 | "midas": "^2.0.3", 83 | "npm-run-all": "^4.1.5", 84 | "postcss": "^8.4.39", 85 | "postcss-dark-theme-class": "^1.3.0", 86 | "postcss-import": "^16.1.0", 87 | "postcss-js": "^4.0.1", 88 | "postcss-load-config": "^5.1.0", 89 | "postcss-nested": "^6.0.1", 90 | "postcss-short": "^5.0.0", 91 | "prettier": "^3.3.2", 92 | "sass": "^1.77.6", 93 | "sass-loader": "^14.2.1", 94 | "standard-version": "^9.3.2", 95 | "strip-ansi": "^7.1.0", 96 | "sugarss": "^4.0.1", 97 | "ts-jest": "^29.4.0", 98 | "ts-node": "^10.9.1", 99 | "typescript-eslint": "^8.38.0", 100 | "webpack": "^5.92.1" 101 | }, 102 | "peerDependencies": { 103 | "@rspack/core": "0.x || 1.x", 104 | "postcss": "^7.0.0 || ^8.0.1", 105 | "webpack": "^5.0.0" 106 | }, 107 | "peerDependenciesMeta": { 108 | "@rspack/core": { 109 | "optional": true 110 | }, 111 | "webpack": { 112 | "optional": true 113 | } 114 | }, 115 | "engines": { 116 | "node": ">= 18.12.0" 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/validate-options.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import { compile, getCompiler } from "./helpers/index"; 4 | 5 | describe("validate options", () => { 6 | const tests = { 7 | execute: { 8 | success: [false], 9 | failure: [1, "test", /test/, [], {}, { foo: "bar" }], 10 | }, 11 | postcssOptions: { 12 | success: [ 13 | { parser: "sugarss" }, 14 | { parser: require("sugarss") }, 15 | { parser: require("sugarss").parse }, 16 | { syntax: "sugarss" }, 17 | { syntax: require("sugarss") }, 18 | { stringifier: "sugarss" }, 19 | { stringifier: require("sugarss") }, 20 | { stringifier: require("sugarss").stringify }, 21 | { 22 | plugins: [ 23 | require("./fixtures/plugin/plugin")(), 24 | require("./fixtures/plugin/plugin"), 25 | ["postcss-short", { prefix: "x" }], 26 | ], 27 | }, 28 | { 29 | plugins: [ 30 | require("./fixtures/plugin/plugin")(), 31 | require("./fixtures/plugin/plugin"), 32 | { "postcss-short": { prefix: "x" } }, 33 | ], 34 | }, 35 | { plugins: { "postcss-short": { prefix: "x" } } }, 36 | { config: true }, 37 | { config: false }, 38 | { config: "test/fixtures/config-scope/config/postcss.config.js" }, 39 | { 40 | config: path.resolve( 41 | __dirname, 42 | "./fixtures/config-scope/config/postcss.config.js", 43 | ), 44 | }, 45 | ], 46 | failure: [{ config: [] }, { config: /test/ }], 47 | }, 48 | sourceMap: { 49 | success: [true, false], 50 | failure: [1, /test/, [], {}, "something"], 51 | }, 52 | implementation: { 53 | success: [require("postcss"), "postcss"], 54 | failure: [1, /test/, [], {}], 55 | }, 56 | }; 57 | 58 | function stringifyValue(value) { 59 | if ( 60 | Array.isArray(value) || 61 | (value && typeof value === "object" && value.constructor === Object) 62 | ) { 63 | return JSON.stringify(value); 64 | } 65 | 66 | return value; 67 | } 68 | 69 | async function createTestCase(key, value, type) { 70 | it(`should ${ 71 | type === "success" ? "successfully validate" : "throw an error on" 72 | } the "${key}" option with "${stringifyValue(value)}" value`, async () => { 73 | let compiler; 74 | 75 | if ( 76 | key === "postcssOptions" && 77 | // eslint-disable-next-line no-prototype-builtins 78 | (value.hasOwnProperty("parser") || value.hasOwnProperty("syntax")) 79 | ) { 80 | compiler = getCompiler( 81 | "./sss/index.js", 82 | {}, 83 | { 84 | module: { 85 | rules: [ 86 | { 87 | test: /\.sss$/i, 88 | use: [ 89 | { 90 | loader: require.resolve("./helpers/testLoader"), 91 | options: {}, 92 | }, 93 | { 94 | loader: path.resolve(__dirname, "../src"), 95 | options: { [key]: value }, 96 | }, 97 | ], 98 | }, 99 | ], 100 | }, 101 | }, 102 | ); 103 | } else { 104 | compiler = getCompiler("./css/index.js", { [key]: value }); 105 | } 106 | 107 | let stats; 108 | 109 | try { 110 | stats = await compile(compiler); 111 | } finally { 112 | if (type === "success") { 113 | expect(stats.hasErrors()).toBe(false); 114 | } else if (type === "failure") { 115 | const { 116 | compilation: { errors }, 117 | } = stats; 118 | 119 | expect(errors).toHaveLength(1); 120 | expect(() => { 121 | throw new Error(errors[0].error.message); 122 | }).toThrowErrorMatchingSnapshot(); 123 | } 124 | } 125 | }); 126 | } 127 | 128 | for (const [key, values] of Object.entries(tests)) { 129 | for (const type of Object.keys(values)) { 130 | for (const value of values[type]) { 131 | createTestCase(key, value, type); 132 | } 133 | } 134 | } 135 | }); 136 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import postcssPackage from "postcss/package.json"; 4 | 5 | import schema from "./options.json"; 6 | import { 7 | exec, 8 | findPackageJSONDir, 9 | getPostcssImplementation, 10 | getPostcssOptions, 11 | loadConfig, 12 | normalizeSourceMap, 13 | normalizeSourceMapAfterPostcss, 14 | reportError, 15 | warningFactory, 16 | } from "./utils"; 17 | 18 | let hasExplicitDependencyOnPostCSS = false; 19 | 20 | /** 21 | * **PostCSS Loader** 22 | * 23 | * Loads && processes CSS with [PostCSS](https://github.com/postcss/postcss) 24 | * 25 | * @method loader 26 | * 27 | * @param {String} content Source 28 | * @param {Object} sourceMap Source Map 29 | * @param {Object} meta Meta 30 | * 31 | * @return {callback} callback Result 32 | */ 33 | export default async function loader(content, sourceMap, meta) { 34 | const options = this.getOptions(schema); 35 | const callback = this.async(); 36 | const configOption = 37 | typeof options.postcssOptions === "undefined" || 38 | typeof options.postcssOptions.config === "undefined" 39 | ? true 40 | : options.postcssOptions.config; 41 | 42 | let implementation; 43 | 44 | try { 45 | implementation = getPostcssImplementation(this, options.implementation); 46 | } catch (error) { 47 | callback(error); 48 | 49 | return; 50 | } 51 | 52 | if (!implementation) { 53 | callback( 54 | new Error( 55 | `The Postcss implementation "${options.implementation}" not found`, 56 | ), 57 | ); 58 | 59 | return; 60 | } 61 | 62 | let loadedConfig; 63 | 64 | if (configOption) { 65 | try { 66 | loadedConfig = await loadConfig( 67 | this, 68 | configOption, 69 | options.postcssOptions, 70 | ); 71 | } catch (error) { 72 | callback(error); 73 | 74 | return; 75 | } 76 | } 77 | 78 | const { plugins, processOptions } = await getPostcssOptions( 79 | this, 80 | loadedConfig, 81 | options.postcssOptions, 82 | ); 83 | 84 | const useSourceMap = 85 | typeof options.sourceMap !== "undefined" 86 | ? options.sourceMap 87 | : this.sourceMap; 88 | 89 | if (useSourceMap) { 90 | processOptions.map = { 91 | inline: false, 92 | annotation: false, 93 | ...processOptions.map, 94 | }; 95 | } 96 | 97 | if (sourceMap && processOptions.map) { 98 | processOptions.map.prev = normalizeSourceMap(sourceMap, this.context); 99 | } 100 | 101 | let root; 102 | 103 | // Reuse PostCSS AST from other loaders 104 | if ( 105 | meta && 106 | meta.ast && 107 | meta.ast.type === "postcss" && 108 | require("semver").satisfies(meta.ast.version, `^${postcssPackage.version}`) 109 | ) { 110 | ({ root } = meta.ast); 111 | } 112 | 113 | if (!root && options.execute) { 114 | content = exec(content, this); 115 | } 116 | 117 | let result; 118 | let processor; 119 | 120 | try { 121 | processor = implementation(plugins); 122 | result = await processor.process(root || content, processOptions); 123 | } catch (error) { 124 | // Check postcss versions to avoid using PostCSS 7. 125 | // For caching reasons, we use the readFileSync and existsSync functions from the context, 126 | // not the functions from the `fs` module. 127 | if ( 128 | !hasExplicitDependencyOnPostCSS && 129 | processor && 130 | processor.version && 131 | processor.version.startsWith("7.") 132 | ) { 133 | // The `findPackageJsonDir` function returns `string` or `null`. 134 | // This is used to do for caching, that is, an explicit comparison with `undefined` 135 | // is used to make the condition body run once. 136 | const packageJSONDir = findPackageJSONDir( 137 | process.cwd(), 138 | this.fs.statSync, 139 | ); 140 | 141 | if (packageJSONDir) { 142 | let bufferOfPackageJSON; 143 | 144 | try { 145 | bufferOfPackageJSON = this.fs.readFileSync( 146 | path.resolve(packageJSONDir, "package.json"), 147 | "utf8", 148 | ); 149 | } catch { 150 | // Nothing 151 | } 152 | 153 | if (bufferOfPackageJSON) { 154 | let pkg; 155 | 156 | try { 157 | pkg = JSON.parse(bufferOfPackageJSON); 158 | } catch { 159 | // Nothing 160 | } 161 | 162 | if (pkg) { 163 | const { dependencies = {}, devDependencies = {} } = pkg; 164 | 165 | if (!dependencies.postcss && !devDependencies.postcss) { 166 | this.emitWarning( 167 | new Error( 168 | "Add postcss as project dependency. postcss is not a peer dependency for postcss-loader. " + 169 | "Use `npm install postcss` or `yarn add postcss`", 170 | ), 171 | ); 172 | } else { 173 | hasExplicitDependencyOnPostCSS = true; 174 | } 175 | } 176 | } 177 | } 178 | } 179 | 180 | reportError(this, callback, error); 181 | 182 | return; 183 | } 184 | 185 | for (const warning of result.warnings()) { 186 | this.emitWarning(warningFactory(warning)); 187 | } 188 | 189 | for (const message of result.messages) { 190 | switch (message.type) { 191 | case "dependency": 192 | this.addDependency(message.file); 193 | break; 194 | case "build-dependency": 195 | this.addBuildDependency(message.file); 196 | break; 197 | case "missing-dependency": 198 | this.addMissingDependency(message.file); 199 | break; 200 | case "context-dependency": 201 | this.addContextDependency(message.file); 202 | break; 203 | case "dir-dependency": 204 | this.addContextDependency(message.dir); 205 | break; 206 | case "asset": 207 | if (message.content && message.file) { 208 | this.emitFile( 209 | message.file, 210 | message.content, 211 | message.sourceMap, 212 | message.info, 213 | ); 214 | } 215 | } 216 | } 217 | 218 | let map = result.map ? result.map.toJSON() : undefined; 219 | 220 | if (map && useSourceMap) { 221 | map = normalizeSourceMapAfterPostcss(map, this.context); 222 | } 223 | 224 | let ast; 225 | 226 | try { 227 | ast = { 228 | type: "postcss", 229 | version: result.processor.version, 230 | root: result.root, 231 | }; 232 | } catch (error) { 233 | reportError(this, callback, error); 234 | 235 | return; 236 | } 237 | 238 | callback(null, result.css, map, { ast }); 239 | } 240 | -------------------------------------------------------------------------------- /test/__snapshots__/loader.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`check postcss versions to avoid using PostCSS 7 should emit a warning if postcss version is not explicitly specified when the loader is failed: errors 1`] = ` 4 | [ 5 | "ModuleBuildError: Module build failed (from \`replaced original path\`): 6 | Error: Something went wrong.", 7 | ] 8 | `; 9 | 10 | exports[`check postcss versions to avoid using PostCSS 7 should emit a warning if postcss version is not explicitly specified when the loader is failed: warnings 1`] = ` 11 | [ 12 | "ModuleWarning: Module Warning (from \`replaced original path\`): 13 | Add postcss as project dependency. postcss is not a peer dependency for postcss-loader. Use \`npm install postcss\` or \`yarn add postcss\`", 14 | ] 15 | `; 16 | 17 | exports[`check postcss versions to avoid using PostCSS 7 should not show a warning if 'package.json' file was not found: errors 1`] = ` 18 | [ 19 | "ModuleBuildError: Module build failed (from \`replaced original path\`): 20 | Error: Something went wrong.", 21 | ] 22 | `; 23 | 24 | exports[`check postcss versions to avoid using PostCSS 7 should not show a warning if 'package.json' file was not found: warnings 1`] = `[]`; 25 | 26 | exports[`check postcss versions to avoid using PostCSS 7 should not show a warning if 'postcss' version is explicitly defined in 'dependencies': errors 1`] = ` 27 | [ 28 | "ModuleBuildError: Module build failed (from \`replaced original path\`): 29 | Error: Something went wrong.", 30 | ] 31 | `; 32 | 33 | exports[`check postcss versions to avoid using PostCSS 7 should not show a warning if 'postcss' version is explicitly defined in 'dependencies': warnings 1`] = `[]`; 34 | 35 | exports[`check postcss versions to avoid using PostCSS 7 should not show a warning if 'postcss' version is explicitly defined in 'devDependencies': errors 1`] = ` 36 | [ 37 | "ModuleBuildError: Module build failed (from \`replaced original path\`): 38 | Error: Something went wrong.", 39 | ] 40 | `; 41 | 42 | exports[`check postcss versions to avoid using PostCSS 7 should not show a warning if 'postcss' version is explicitly defined in 'devDependencies': warnings 1`] = `[]`; 43 | 44 | exports[`check postcss versions to avoid using PostCSS 7 should work when 'processor' throw an error: errors 1`] = ` 45 | [ 46 | "ModuleBuildError: Module build failed (from \`replaced original path\`): 47 | Error: Error in implementation", 48 | ] 49 | `; 50 | 51 | exports[`check postcss versions to avoid using PostCSS 7 should work when 'processor' throw an error: warnings 1`] = `[]`; 52 | 53 | exports[`loader should emit asset using the "messages" API: errors 1`] = `[]`; 54 | 55 | exports[`loader should emit asset using the "messages" API: warnings 1`] = `[]`; 56 | 57 | exports[`loader should emit warning using the "messages" API: css 1`] = ` 58 | "a { 59 | color: black; 60 | } 61 | 62 | a { 63 | color: red; 64 | } 65 | 66 | a { 67 | color: green; 68 | } 69 | 70 | a { 71 | color: blue; 72 | } 73 | 74 | .class { 75 | -x-border-color: blue blue *; 76 | -x-color: * #fafafa; 77 | } 78 | 79 | .class-foo { 80 | -z-border-color: blue blue *; 81 | -z-color: * #fafafa; 82 | } 83 | 84 | .phone { 85 | &_title { 86 | width: 500px; 87 | 88 | @media (max-width: 500px) { 89 | width: auto; 90 | } 91 | 92 | body.is_dark & { 93 | color: white; 94 | } 95 | } 96 | 97 | img { 98 | display: block; 99 | } 100 | } 101 | " 102 | `; 103 | 104 | exports[`loader should emit warning using the "messages" API: errors 1`] = `[]`; 105 | 106 | exports[`loader should emit warning using the "messages" API: warnings 1`] = ` 107 | [ 108 | "ModuleWarning: Module Warning (from \`replaced original path\`): 109 | (10:3) from "postcss-plugin" plugin: 110 | 111 | Code: 112 | color: green 113 | ", 114 | "ModuleWarning: Module Warning (from \`replaced original path\`): 115 | (14:3) from "postcss-plugin" plugin: 116 | 117 | Code: 118 | color: blue 119 | ", 120 | "ModuleWarning: Module Warning (from \`replaced original path\`): 121 | (18:3) from "postcss-plugin" plugin: 122 | 123 | Code: 124 | -x-border-color: blue blue * 125 | ", 126 | "ModuleWarning: Module Warning (from \`replaced original path\`): 127 | (19:3) from "postcss-plugin" plugin: 128 | 129 | Code: 130 | -x-color: * #fafafa 131 | ", 132 | "ModuleWarning: Module Warning (from \`replaced original path\`): 133 | (23:3) from "postcss-plugin" plugin: 134 | 135 | Code: 136 | -z-border-color: blue blue * 137 | ", 138 | "ModuleWarning: Module Warning (from \`replaced original path\`): 139 | (24:3) from "postcss-plugin" plugin: 140 | 141 | Code: 142 | -z-color: * #fafafa 143 | ", 144 | "ModuleWarning: Module Warning (from \`replaced original path\`): 145 | (29:5) from "postcss-plugin" plugin: 146 | 147 | Code: 148 | width: 500px 149 | ", 150 | "ModuleWarning: Module Warning (from \`replaced original path\`): 151 | (2:3) from "postcss-plugin" plugin: 152 | 153 | Code: 154 | color: black 155 | ", 156 | "ModuleWarning: Module Warning (from \`replaced original path\`): 157 | (32:7) from "postcss-plugin" plugin: 158 | 159 | Code: 160 | width: auto 161 | ", 162 | "ModuleWarning: Module Warning (from \`replaced original path\`): 163 | (36:7) from "postcss-plugin" plugin: 164 | 165 | Code: 166 | color: white 167 | ", 168 | "ModuleWarning: Module Warning (from \`replaced original path\`): 169 | (41:5) from "postcss-plugin" plugin: 170 | 171 | Code: 172 | display: block 173 | ", 174 | "ModuleWarning: Module Warning (from \`replaced original path\`): 175 | (6:3) from "postcss-plugin" plugin: 176 | 177 | Code: 178 | color: red 179 | ", 180 | ] 181 | `; 182 | 183 | exports[`loader should register dependencies using the "messages" API: errors 1`] = `[]`; 184 | 185 | exports[`loader should register dependencies using the "messages" API: warnings 1`] = `[]`; 186 | 187 | exports[`loader should reuse PostCSS AST: css 1`] = ` 188 | "a { 189 | color: black; 190 | } 191 | 192 | a { 193 | color: red; 194 | } 195 | 196 | a { 197 | color: green; 198 | } 199 | 200 | a { 201 | color: blue; 202 | } 203 | 204 | .class { 205 | -x-border-color: blue blue *; 206 | -x-color: * #fafafa; 207 | } 208 | 209 | .class-foo { 210 | -z-border-color: blue blue *; 211 | -z-color: * #fafafa; 212 | } 213 | 214 | .phone { 215 | &_title { 216 | width: 500px; 217 | 218 | @media (max-width: 500px) { 219 | width: auto; 220 | } 221 | 222 | body.is_dark & { 223 | color: white; 224 | } 225 | } 226 | 227 | img { 228 | display: block; 229 | } 230 | } 231 | " 232 | `; 233 | 234 | exports[`loader should reuse PostCSS AST: errors 1`] = `[]`; 235 | 236 | exports[`loader should reuse PostCSS AST: warnings 1`] = `[]`; 237 | 238 | exports[`loader should throw an error on invalid syntax: errors 1`] = ` 239 | [ 240 | "ModuleBuildError: Module build failed (from \`replaced original path\`): 241 | 242 | SyntaxError 243 | 244 | (1:3) /test/fixtures/css/style.css Unnecessary curly bracket 245 | 246 | > 1 | a { 247 | | ^ 248 | 2 | color: black; 249 | 3 | } 250 | ", 251 | ] 252 | `; 253 | 254 | exports[`loader should throw an error on invalid syntax: warnings 1`] = `[]`; 255 | 256 | exports[`loader should work with SugarSS: css 1`] = ` 257 | "a { 258 | color: black 259 | } 260 | " 261 | `; 262 | 263 | exports[`loader should work with SugarSS: errors 1`] = `[]`; 264 | 265 | exports[`loader should work with SugarSS: warnings 1`] = `[]`; 266 | 267 | exports[`loader should work: css 1`] = ` 268 | "a { 269 | color: black; 270 | } 271 | 272 | a { 273 | color: red; 274 | } 275 | 276 | a { 277 | color: green; 278 | } 279 | 280 | a { 281 | color: blue; 282 | } 283 | 284 | .class { 285 | -x-border-color: blue blue *; 286 | -x-color: * #fafafa; 287 | } 288 | 289 | .class-foo { 290 | -z-border-color: blue blue *; 291 | -z-color: * #fafafa; 292 | } 293 | 294 | .phone { 295 | &_title { 296 | width: 500px; 297 | 298 | @media (max-width: 500px) { 299 | width: auto; 300 | } 301 | 302 | body.is_dark & { 303 | color: white; 304 | } 305 | } 306 | 307 | img { 308 | display: block; 309 | } 310 | } 311 | " 312 | `; 313 | 314 | exports[`loader should work: errors 1`] = `[]`; 315 | 316 | exports[`loader should work: warnings 1`] = `[]`; 317 | -------------------------------------------------------------------------------- /test/config-autoload.test.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | import { loadConfig } from "../src/utils"; 5 | 6 | const testDirectory = path.resolve(__dirname, "fixtures", "config-autoload"); 7 | 8 | const loaderContext = { 9 | fs, 10 | addBuildDependency: () => true, 11 | addDependency: () => true, 12 | }; 13 | 14 | describe("autoload config", () => { 15 | it('should load ".postcssrc"', async () => { 16 | const loadedConfig = await loadConfig( 17 | loaderContext, 18 | path.resolve(testDirectory, "rc"), 19 | ); 20 | 21 | expect(loadedConfig.config.map).toBe(false); 22 | expect(loadedConfig.config.from).toBe("./test/rc/fixtures/index.css"); 23 | expect(loadedConfig.config.to).toBe("./test/rc/expect/index.css"); 24 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(2); 25 | expect(loadedConfig.filepath).toEqual( 26 | path.resolve(testDirectory, "rc", ".postcssrc"), 27 | ); 28 | }); 29 | 30 | it('should load ".postcssrc.js"', async () => { 31 | const loadedConfig = await loadConfig( 32 | loaderContext, 33 | path.resolve(testDirectory, "rc-js"), 34 | ); 35 | 36 | expect(loadedConfig.config.map).toBe(false); 37 | expect(loadedConfig.config.from).toBe("./test/rc-js/fixtures/index.css"); 38 | expect(loadedConfig.config.to).toBe("./test/rc-js/expect/index.css"); 39 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(2); 40 | expect(loadedConfig.filepath).toEqual( 41 | path.resolve(testDirectory, "rc-js", ".postcssrc.js"), 42 | ); 43 | }); 44 | 45 | it('should load "package.json"', async () => { 46 | const loadedConfig = await loadConfig( 47 | loaderContext, 48 | path.resolve(testDirectory, "pkg"), 49 | ); 50 | 51 | expect(loadedConfig.config.parser).toBe(false); 52 | expect(loadedConfig.config.syntax).toBe(false); 53 | expect(loadedConfig.config.map).toBe(false); 54 | expect(loadedConfig.config.from).toBe("./index.css"); 55 | expect(loadedConfig.config.to).toBe("./index.css"); 56 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(2); 57 | expect(loadedConfig.filepath).toEqual( 58 | path.resolve(testDirectory, "pkg", "package.json"), 59 | ); 60 | }); 61 | 62 | it('should load "postcss.config.js" with "Object" syntax of plugins', async () => { 63 | const loadedConfig = await loadConfig( 64 | loaderContext, 65 | path.resolve(testDirectory, "js/object"), 66 | ); 67 | 68 | expect(loadedConfig.config.map).toBe(false); 69 | expect(loadedConfig.config.from).toBe( 70 | "./test/fixtures/config-autoload/js/object/index.css", 71 | ); 72 | expect(loadedConfig.config.to).toBe( 73 | "./test/fixtures/config-autoload/js/object/expect/index.css", 74 | ); 75 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(2); 76 | expect(loadedConfig.filepath).toEqual( 77 | path.resolve(testDirectory, "js/object", "postcss.config.js"), 78 | ); 79 | }); 80 | 81 | it('should load "postcss.config.cts" with "Object" syntax of plugins', async () => { 82 | const loadedConfig = await loadConfig( 83 | loaderContext, 84 | path.resolve(testDirectory, "ts/object"), 85 | ); 86 | 87 | expect(loadedConfig.config.map).toBe(false); 88 | expect(loadedConfig.config.from).toBe( 89 | "./test/fixtures/config-autoload/ts/object/index.css", 90 | ); 91 | expect(loadedConfig.config.to).toBe( 92 | "./test/fixtures/config-autoload/ts/object/expect/index.css", 93 | ); 94 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(2); 95 | expect(loadedConfig.filepath).toEqual( 96 | path.resolve(testDirectory, "ts/object", "postcss.config.ts"), 97 | ); 98 | }); 99 | 100 | it('should load "postcss.config.js" with "Array" syntax of plugins', async () => { 101 | const loadedConfig = await loadConfig( 102 | loaderContext, 103 | path.resolve(testDirectory, "js/array"), 104 | ); 105 | 106 | expect(loadedConfig.config.map).toBe(false); 107 | expect(loadedConfig.config.from).toBe( 108 | "./test/fixtures/config-autoload/js/object/index.css", 109 | ); 110 | expect(loadedConfig.config.to).toBe( 111 | "./test/fixtures/config-autoload/js/object/expect/index.css", 112 | ); 113 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(4); 114 | expect(loadedConfig.filepath).toEqual( 115 | path.resolve(testDirectory, "js/array", "postcss.config.js"), 116 | ); 117 | }); 118 | 119 | it('should load ESM version of "postcss.config.js" with "Array" syntax of plugins', async () => { 120 | const loadedConfig = await loadConfig( 121 | loaderContext, 122 | path.resolve(testDirectory, "js/array-esm-js"), 123 | ); 124 | 125 | expect(loadedConfig.config.map).toBe(false); 126 | expect(loadedConfig.config.from).toBe( 127 | "./test/fixtures/config-autoload/js/object/index.css", 128 | ); 129 | expect(loadedConfig.config.to).toBe( 130 | "./test/fixtures/config-autoload/js/object/expect/index.css", 131 | ); 132 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(4); 133 | expect(loadedConfig.filepath).toEqual( 134 | path.resolve(testDirectory, "js/array-esm-js", "postcss.config.js"), 135 | ); 136 | }); 137 | 138 | // TODO Test manually with NODE_OPTIONS=--experimental-vm-modules to enable ESM support in jest 139 | // eslint-disable-next-line jest/no-disabled-tests 140 | it.skip('should load "postcss.config.mjs" with "Array" syntax of plugins', async () => { 141 | const loadedConfig = await loadConfig( 142 | loaderContext, 143 | path.resolve(testDirectory, "js/array-mjs"), 144 | ); 145 | 146 | expect(loadedConfig.config.map).toBe(false); 147 | expect(loadedConfig.config.from).toBe( 148 | "./test/fixtures/config-autoload/js/object/index.css", 149 | ); 150 | expect(loadedConfig.config.to).toBe( 151 | "./test/fixtures/config-autoload/js/object/expect/index.css", 152 | ); 153 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(4); 154 | expect(loadedConfig.filepath).toEqual( 155 | path.resolve(testDirectory, "js/array-mjs", "postcss.config.mjs"), 156 | ); 157 | }); 158 | 159 | it('should load "postcss.config.ts" with "Array" syntax of plugins', async () => { 160 | const loadedConfig = await loadConfig( 161 | loaderContext, 162 | path.resolve(testDirectory, "ts/array"), 163 | ); 164 | 165 | expect(loadedConfig.config.map).toBe(false); 166 | expect(loadedConfig.config.from).toBe( 167 | "./test/fixtures/config-autoload/ts/object/index.css", 168 | ); 169 | expect(loadedConfig.config.to).toBe( 170 | "./test/fixtures/config-autoload/ts/object/expect/index.css", 171 | ); 172 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(4); 173 | expect(loadedConfig.filepath).toEqual( 174 | path.resolve(testDirectory, "ts/array", "postcss.config.ts"), 175 | ); 176 | }); 177 | 178 | it('should load "postcss.config.mts" with "Array" syntax of plugins', async () => { 179 | const loadedConfig = await loadConfig( 180 | loaderContext, 181 | path.resolve(testDirectory, "ts/array-mts"), 182 | ); 183 | 184 | expect(loadedConfig.config.map).toBe(false); 185 | expect(loadedConfig.config.from).toBe( 186 | "./test/fixtures/config-autoload/ts/object/index.css", 187 | ); 188 | expect(loadedConfig.config.to).toBe( 189 | "./test/fixtures/config-autoload/ts/object/expect/index.css", 190 | ); 191 | expect(Object.keys(loadedConfig.config.plugins)).toHaveLength(4); 192 | expect(loadedConfig.filepath).toEqual( 193 | path.resolve(testDirectory, "ts/array-mts", "postcss.config.mts"), 194 | ); 195 | }); 196 | 197 | it('should load empty ".postcssrc"', async () => { 198 | const loadedConfig = await loadConfig( 199 | loaderContext, 200 | path.resolve(testDirectory, "empty/.postcssrc"), 201 | ); 202 | 203 | expect(loadedConfig.config).toBeUndefined(); 204 | expect(loadedConfig.filepath).toEqual( 205 | path.resolve(testDirectory, "empty/.postcssrc"), 206 | ); 207 | }); 208 | 209 | it('should throw an error on "unresolved" config', async () => { 210 | try { 211 | await loadConfig(loaderContext, path.resolve("unresolved")); 212 | } catch (error) { 213 | expect(error.message).toMatch(/^No PostCSS config found in: (.*)$/); 214 | } 215 | }); 216 | }); 217 | -------------------------------------------------------------------------------- /test/__snapshots__/validate-options.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`validate options should throw an error on the "execute" option with "/test/" value 1`] = ` 4 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 5 | - options.execute should be a boolean. 6 | -> Enables/Disables PostCSS parser support in 'CSS-in-JS'. 7 | -> Read more at https://github.com/webpack/postcss-loader#execute" 8 | `; 9 | 10 | exports[`validate options should throw an error on the "execute" option with "[]" value 1`] = ` 11 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 12 | - options.execute should be a boolean. 13 | -> Enables/Disables PostCSS parser support in 'CSS-in-JS'. 14 | -> Read more at https://github.com/webpack/postcss-loader#execute" 15 | `; 16 | 17 | exports[`validate options should throw an error on the "execute" option with "{"foo":"bar"}" value 1`] = ` 18 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 19 | - options.execute should be a boolean. 20 | -> Enables/Disables PostCSS parser support in 'CSS-in-JS'. 21 | -> Read more at https://github.com/webpack/postcss-loader#execute" 22 | `; 23 | 24 | exports[`validate options should throw an error on the "execute" option with "{}" value 1`] = ` 25 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 26 | - options.execute should be a boolean. 27 | -> Enables/Disables PostCSS parser support in 'CSS-in-JS'. 28 | -> Read more at https://github.com/webpack/postcss-loader#execute" 29 | `; 30 | 31 | exports[`validate options should throw an error on the "execute" option with "1" value 1`] = ` 32 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 33 | - options.execute should be a boolean. 34 | -> Enables/Disables PostCSS parser support in 'CSS-in-JS'. 35 | -> Read more at https://github.com/webpack/postcss-loader#execute" 36 | `; 37 | 38 | exports[`validate options should throw an error on the "execute" option with "test" value 1`] = ` 39 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 40 | - options.execute should be a boolean. 41 | -> Enables/Disables PostCSS parser support in 'CSS-in-JS'. 42 | -> Read more at https://github.com/webpack/postcss-loader#execute" 43 | `; 44 | 45 | exports[`validate options should throw an error on the "implementation" option with "/test/" value 1`] = ` 46 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 47 | - options.implementation should be one of these: 48 | string | function 49 | -> The implementation of postcss to use, instead of the locally installed version 50 | -> Read more at https://github.com/webpack/postcss-loader#implementation 51 | Details: 52 | * options.implementation should be a string. 53 | * options.implementation should be an instance of function." 54 | `; 55 | 56 | exports[`validate options should throw an error on the "implementation" option with "[]" value 1`] = ` 57 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 58 | - options.implementation should be one of these: 59 | string | function 60 | -> The implementation of postcss to use, instead of the locally installed version 61 | -> Read more at https://github.com/webpack/postcss-loader#implementation 62 | Details: 63 | * options.implementation should be a string. 64 | * options.implementation should be an instance of function." 65 | `; 66 | 67 | exports[`validate options should throw an error on the "implementation" option with "{}" value 1`] = ` 68 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 69 | - options.implementation should be one of these: 70 | string | function 71 | -> The implementation of postcss to use, instead of the locally installed version 72 | -> Read more at https://github.com/webpack/postcss-loader#implementation 73 | Details: 74 | * options.implementation should be a string. 75 | * options.implementation should be an instance of function." 76 | `; 77 | 78 | exports[`validate options should throw an error on the "implementation" option with "1" value 1`] = ` 79 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 80 | - options.implementation should be one of these: 81 | string | function 82 | -> The implementation of postcss to use, instead of the locally installed version 83 | -> Read more at https://github.com/webpack/postcss-loader#implementation 84 | Details: 85 | * options.implementation should be a string. 86 | * options.implementation should be an instance of function." 87 | `; 88 | 89 | exports[`validate options should throw an error on the "postcssOptions" option with "{"config":[]}" value 1`] = ` 90 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 91 | - options.postcssOptions should be one of these: 92 | object { config?, … } | function 93 | -> Options to pass through to \`Postcss\`. 94 | -> Read more at https://github.com/webpack/postcss-loader#postcssOptions 95 | Details: 96 | * options.postcssOptions.config should be one of these: 97 | string | boolean 98 | -> Allows to specify PostCSS config path. 99 | -> Read more at https://github.com/webpack/postcss-loader#config 100 | Details: 101 | * options.postcssOptions.config should be a string. 102 | -> Allows to specify the path to the configuration file 103 | * options.postcssOptions.config should be a boolean. 104 | -> Enables/Disables autoloading config" 105 | `; 106 | 107 | exports[`validate options should throw an error on the "postcssOptions" option with "{"config":{}}" value 1`] = ` 108 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 109 | - options.postcssOptions should be one of these: 110 | object { config?, … } | function 111 | -> Options to pass through to \`Postcss\`. 112 | -> Read more at https://github.com/webpack/postcss-loader#postcssOptions 113 | Details: 114 | * options.postcssOptions.config should be one of these: 115 | string | boolean 116 | -> Allows to specify PostCSS config path. 117 | -> Read more at https://github.com/webpack/postcss-loader#config 118 | Details: 119 | * options.postcssOptions.config should be a string. 120 | -> Allows to specify the path to the configuration file 121 | * options.postcssOptions.config should be a boolean. 122 | -> Enables/Disables autoloading config" 123 | `; 124 | 125 | exports[`validate options should throw an error on the "sourceMap" option with "/test/" value 1`] = ` 126 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 127 | - options.sourceMap should be a boolean. 128 | -> Enables/Disables generation of source maps. 129 | -> Read more at https://github.com/webpack/postcss-loader#sourcemap" 130 | `; 131 | 132 | exports[`validate options should throw an error on the "sourceMap" option with "[]" value 1`] = ` 133 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 134 | - options.sourceMap should be a boolean. 135 | -> Enables/Disables generation of source maps. 136 | -> Read more at https://github.com/webpack/postcss-loader#sourcemap" 137 | `; 138 | 139 | exports[`validate options should throw an error on the "sourceMap" option with "{}" value 1`] = ` 140 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 141 | - options.sourceMap should be a boolean. 142 | -> Enables/Disables generation of source maps. 143 | -> Read more at https://github.com/webpack/postcss-loader#sourcemap" 144 | `; 145 | 146 | exports[`validate options should throw an error on the "sourceMap" option with "1" value 1`] = ` 147 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 148 | - options.sourceMap should be a boolean. 149 | -> Enables/Disables generation of source maps. 150 | -> Read more at https://github.com/webpack/postcss-loader#sourcemap" 151 | `; 152 | 153 | exports[`validate options should throw an error on the "sourceMap" option with "something" value 1`] = ` 154 | "Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema. 155 | - options.sourceMap should be a boolean. 156 | -> Enables/Disables generation of source maps. 157 | -> Read more at https://github.com/webpack/postcss-loader#sourcemap" 158 | `; 159 | -------------------------------------------------------------------------------- /test/loader.test.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import postcss from "postcss"; 4 | 5 | import * as utils from "../src/utils"; 6 | 7 | import { 8 | compile, 9 | getCodeFromBundle, 10 | getCompiler, 11 | getErrors, 12 | getWarnings, 13 | } from "./helpers/index"; 14 | 15 | describe("loader", () => { 16 | it("should work", async () => { 17 | const compiler = getCompiler("./css/index.js"); 18 | const stats = await compile(compiler); 19 | const codeFromBundle = getCodeFromBundle("style.css", stats); 20 | 21 | expect(codeFromBundle.css).toMatchSnapshot("css"); 22 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 23 | expect(getErrors(stats)).toMatchSnapshot("errors"); 24 | }); 25 | 26 | it("should throw an error on invalid syntax", async () => { 27 | const compiler = getCompiler("./css/index.js", { 28 | postcssOptions: { 29 | hideNothingWarning: true, 30 | parser: "sugarss", 31 | }, 32 | }); 33 | const stats = await compile(compiler); 34 | 35 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 36 | expect(getErrors(stats)).toMatchSnapshot("errors"); 37 | }); 38 | 39 | it('should emit warning using the "messages" API', async () => { 40 | const plugin = () => (css, result) => { 41 | css.walkDecls((node) => { 42 | node.warn(result, ""); 43 | }); 44 | }; 45 | 46 | const postcssPlugin = postcss.plugin("postcss-plugin", plugin); 47 | 48 | const compiler = getCompiler("./css/index.js", { 49 | postcssOptions: { 50 | plugins: [postcssPlugin()], 51 | }, 52 | }); 53 | const stats = await compile(compiler); 54 | 55 | const codeFromBundle = getCodeFromBundle("style.css", stats); 56 | 57 | expect(codeFromBundle.css).toMatchSnapshot("css"); 58 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 59 | expect(getErrors(stats)).toMatchSnapshot("errors"); 60 | }); 61 | 62 | it('should emit asset using the "messages" API', async () => { 63 | const plugin = () => (css, result) => { 64 | result.messages.push({ 65 | type: "asset", 66 | file: "sprite.svg", 67 | content: "...", 68 | plugin, 69 | }); 70 | }; 71 | 72 | const postcssPlugin = postcss.plugin("postcss-assets", plugin); 73 | const compiler = getCompiler("./css/index.js", { 74 | postcssOptions: { 75 | plugins: [postcssPlugin()], 76 | }, 77 | }); 78 | const stats = await compile(compiler); 79 | 80 | expect(stats.compilation.assets["sprite.svg"]).toBeDefined(); 81 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 82 | expect(getErrors(stats)).toMatchSnapshot("errors"); 83 | }); 84 | 85 | it('should register dependencies using the "messages" API', async () => { 86 | const plugin = () => (css, result) => { 87 | result.messages.push( 88 | { 89 | type: "build-dependency", 90 | file: path.resolve(__dirname, "fixtures", "build-dep.html"), 91 | content: "", 92 | plugin, 93 | }, 94 | { 95 | type: "missing-dependency", 96 | file: path.resolve(__dirname, "fixtures", "missing-dep.html"), 97 | content: "", 98 | plugin, 99 | }, 100 | { 101 | type: "context-dependency", 102 | file: path.resolve(__dirname, "fixtures", "deps"), 103 | content: "", 104 | plugin, 105 | }, 106 | { 107 | type: "dir-dependency", 108 | dir: path.resolve(__dirname, "fixtures", "deps2"), 109 | content: "", 110 | plugin, 111 | }, 112 | ); 113 | }; 114 | 115 | const postcssPlugin = postcss.plugin("postcss-plugin", plugin); 116 | const compiler = getCompiler("./css/index.js", { 117 | postcssOptions: { 118 | plugins: [postcssPlugin()], 119 | }, 120 | }); 121 | 122 | const stats = await compile(compiler); 123 | const { contextDependencies, missingDependencies, buildDependencies } = 124 | stats.compilation; 125 | 126 | expect(contextDependencies).toContain( 127 | path.resolve(__dirname, "fixtures", "deps"), 128 | ); 129 | expect(contextDependencies).toContain( 130 | path.resolve(__dirname, "fixtures", "deps2"), 131 | ); 132 | expect(missingDependencies).toContain( 133 | path.resolve(__dirname, "fixtures", "missing-dep.html"), 134 | ); 135 | expect(buildDependencies).toContain( 136 | path.resolve(__dirname, "fixtures", "build-dep.html"), 137 | ); 138 | 139 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 140 | expect(getErrors(stats)).toMatchSnapshot("errors"); 141 | }); 142 | 143 | it("should reuse PostCSS AST", async () => { 144 | const spy = jest.fn(); 145 | const compiler = getCompiler( 146 | "./css/index.js", 147 | {}, 148 | { 149 | module: { 150 | rules: [ 151 | { 152 | test: /\.(css|sss)$/i, 153 | use: [ 154 | { 155 | loader: require.resolve("./helpers/testLoader"), 156 | options: {}, 157 | }, 158 | { 159 | loader: path.resolve(__dirname, "../src"), 160 | options: { 161 | postcssOptions: { hideNothingWarning: true }, 162 | }, 163 | }, 164 | { 165 | loader: require.resolve("./helpers/astLoader"), 166 | options: { spy }, 167 | }, 168 | ], 169 | }, 170 | ], 171 | }, 172 | }, 173 | ); 174 | const stats = await compile(compiler); 175 | const codeFromBundle = getCodeFromBundle("style.css", stats); 176 | 177 | expect(spy).toHaveBeenCalledTimes(1); 178 | expect(codeFromBundle.css).toMatchSnapshot("css"); 179 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 180 | expect(getErrors(stats)).toMatchSnapshot("errors"); 181 | }); 182 | 183 | it("should work with SugarSS", async () => { 184 | const compiler = getCompiler("./sss/index.js", { 185 | postcssOptions: { 186 | parser: "sugarss", 187 | hideNothingWarning: true, 188 | }, 189 | }); 190 | const stats = await compile(compiler); 191 | 192 | const codeFromBundle = getCodeFromBundle("style.sss", stats); 193 | 194 | expect(codeFromBundle.css).toMatchSnapshot("css"); 195 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 196 | expect(getErrors(stats)).toMatchSnapshot("errors"); 197 | }); 198 | }); 199 | 200 | describe("check postcss versions to avoid using PostCSS 7", () => { 201 | it("should emit a warning if postcss version is not explicitly specified when the loader is failed", async () => { 202 | const spy = jest 203 | .spyOn(utils, "findPackageJSONDir") 204 | .mockReturnValue( 205 | path.resolve(__dirname, "./fixtures/package-json-files/no-postcss"), 206 | ); 207 | 208 | const compiler = getCompiler("./css/index.js", { 209 | implementation: (...args) => { 210 | const result = postcss(...args); 211 | 212 | result.version = "7.0.0"; 213 | result.process = () => 214 | Promise.reject(new Error("Something went wrong.")); 215 | 216 | return result; 217 | }, 218 | }); 219 | const stats = await compile(compiler); 220 | 221 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 222 | expect(getErrors(stats, true)).toMatchSnapshot("errors"); 223 | 224 | spy.mockRestore(); 225 | }); 226 | 227 | it("should not show a warning if 'postcss' version is explicitly defined in 'dependencies'", async () => { 228 | const spy = jest 229 | .spyOn(utils, "findPackageJSONDir") 230 | .mockReturnValue( 231 | path.resolve( 232 | __dirname, 233 | "./fixtures/package-json-files/postcss-v8-in-dependencies", 234 | ), 235 | ); 236 | 237 | const compiler = getCompiler("./css/index.js", { 238 | implementation: (...args) => { 239 | const result = postcss(...args); 240 | 241 | result.version = "7.0.0"; 242 | result.process = () => 243 | Promise.reject(new Error("Something went wrong.")); 244 | 245 | return result; 246 | }, 247 | }); 248 | const stats = await compile(compiler); 249 | 250 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 251 | expect(getErrors(stats, true)).toMatchSnapshot("errors"); 252 | 253 | spy.mockRestore(); 254 | }); 255 | 256 | it("should not show a warning if 'postcss' version is explicitly defined in 'devDependencies'", async () => { 257 | const spy = jest 258 | .spyOn(utils, "findPackageJSONDir") 259 | .mockReturnValue( 260 | path.resolve( 261 | __dirname, 262 | "./fixtures/package-json-files/postcss-v8-in-devDependencies", 263 | ), 264 | ); 265 | 266 | const compiler = getCompiler("./css/index.js", { 267 | implementation: (...args) => { 268 | const result = postcss(...args); 269 | 270 | result.version = "7.0.0"; 271 | result.process = () => 272 | Promise.reject(new Error("Something went wrong.")); 273 | 274 | return result; 275 | }, 276 | }); 277 | const stats = await compile(compiler); 278 | 279 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 280 | expect(getErrors(stats, true)).toMatchSnapshot("errors"); 281 | 282 | spy.mockRestore(); 283 | }); 284 | 285 | it("should not show a warning if 'package.json' file was not found", async () => { 286 | const spy = jest 287 | .spyOn(utils, "findPackageJSONDir") 288 | .mockReturnValue( 289 | path.resolve(__dirname, "./fixtures/package-json-files/unknown"), 290 | ); 291 | 292 | const compiler = getCompiler("./css/index.js", { 293 | implementation: (...args) => { 294 | const result = postcss(...args); 295 | 296 | result.version = "7.0.0"; 297 | result.process = () => 298 | Promise.reject(new Error("Something went wrong.")); 299 | 300 | return result; 301 | }, 302 | }); 303 | const stats = await compile(compiler); 304 | 305 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 306 | expect(getErrors(stats, true)).toMatchSnapshot("errors"); 307 | 308 | spy.mockRestore(); 309 | }); 310 | 311 | it("should work when 'processor' throw an error", async () => { 312 | const spy = jest 313 | .spyOn(utils, "findPackageJSONDir") 314 | .mockReturnValue( 315 | path.resolve(__dirname, "./fixtures/package-json-files/no-postcss"), 316 | ); 317 | 318 | const compiler = getCompiler("./css/index.js", { 319 | implementation: () => { 320 | throw new Error("Error in implementation"); 321 | }, 322 | }); 323 | const stats = await compile(compiler); 324 | 325 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 326 | expect(getErrors(stats, true)).toMatchSnapshot("errors"); 327 | 328 | spy.mockRestore(); 329 | }); 330 | }); 331 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import Module from "node:module"; 2 | import path from "node:path"; 3 | import url from "node:url"; 4 | 5 | import { cosmiconfig, defaultLoadersSync } from "cosmiconfig"; 6 | 7 | const parentModule = module; 8 | 9 | const stat = (inputFileSystem, filePath) => 10 | new Promise((resolve, reject) => { 11 | inputFileSystem.stat(filePath, (err, stats) => { 12 | if (err) { 13 | reject(err); 14 | } 15 | resolve(stats); 16 | }); 17 | }); 18 | 19 | function exec(code, loaderContext) { 20 | const { resource, context } = loaderContext; 21 | 22 | const module = new Module(resource, parentModule); 23 | 24 | module.paths = Module._nodeModulePaths(context); 25 | module.filename = resource; 26 | 27 | module._compile(code, resource); 28 | 29 | return module.exports; 30 | } 31 | 32 | let tsLoader; 33 | 34 | async function loadConfig(loaderContext, config, postcssOptions) { 35 | const searchPath = 36 | typeof config === "string" 37 | ? path.resolve(config) 38 | : path.dirname(loaderContext.resourcePath); 39 | 40 | let stats; 41 | 42 | try { 43 | stats = await stat(loaderContext.fs, searchPath); 44 | } catch { 45 | throw new Error(`No PostCSS config found in: ${searchPath}`); 46 | } 47 | 48 | const moduleName = "postcss"; 49 | const searchPlaces = [ 50 | // Prefer popular format 51 | "package.json", 52 | `${moduleName}.config.js`, 53 | `${moduleName}.config.mjs`, 54 | `${moduleName}.config.cjs`, 55 | `${moduleName}.config.ts`, 56 | `${moduleName}.config.mts`, 57 | `${moduleName}.config.cts`, 58 | `.${moduleName}rc`, 59 | `.${moduleName}rc.json`, 60 | `.${moduleName}rc.js`, 61 | `.${moduleName}rc.mjs`, 62 | `.${moduleName}rc.cjs`, 63 | `.${moduleName}rc.ts`, 64 | `.${moduleName}rc.mts`, 65 | `.${moduleName}rc.cts`, 66 | `.${moduleName}rc.yaml`, 67 | `.${moduleName}rc.yml`, 68 | `.config/${moduleName}rc`, 69 | `.config/${moduleName}rc.json`, 70 | `.config/${moduleName}rc.yaml`, 71 | `.config/${moduleName}rc.yml`, 72 | `.config/${moduleName}rc.js`, 73 | `.config/${moduleName}rc.mjs`, 74 | `.config/${moduleName}rc.cjs`, 75 | `.config/${moduleName}rc.ts`, 76 | `.config/${moduleName}rc.mts`, 77 | `.config/${moduleName}rc.cts`, 78 | ]; 79 | 80 | const loaders = { 81 | ".js": async (...args) => { 82 | let result; 83 | 84 | try { 85 | result = defaultLoadersSync[".js"](...args); 86 | } catch (error) { 87 | let importESM; 88 | 89 | try { 90 | // eslint-disable-next-line no-new-func 91 | importESM = new Function("id", "return import(id);"); 92 | } catch { 93 | importESM = null; 94 | } 95 | 96 | if ( 97 | error.code === "ERR_REQUIRE_ESM" && 98 | url.pathToFileURL && 99 | importESM 100 | ) { 101 | const urlForConfig = url.pathToFileURL(args[0]); 102 | 103 | result = await importESM(urlForConfig); 104 | } else { 105 | throw error; 106 | } 107 | } 108 | 109 | if (result.default) { 110 | return result.default; 111 | } 112 | 113 | return result; 114 | }, 115 | ".cjs": defaultLoadersSync[".cjs"], 116 | ".mjs": async (...args) => { 117 | let result; 118 | let importESM; 119 | 120 | try { 121 | // eslint-disable-next-line no-new-func 122 | importESM = new Function("id", "return import(id);"); 123 | } catch { 124 | importESM = null; 125 | } 126 | 127 | if (url.pathToFileURL && importESM) { 128 | const urlForConfig = url.pathToFileURL(args[0]); 129 | 130 | result = await importESM(urlForConfig); 131 | } else { 132 | throw new Error("ESM is not supported"); 133 | } 134 | 135 | if (result.default) { 136 | return result.default; 137 | } 138 | 139 | return result; 140 | }, 141 | }; 142 | 143 | if (!tsLoader) { 144 | const opts = { interopDefault: true }; 145 | 146 | const { createJiti } = require("jiti"); 147 | 148 | const jiti = createJiti(__filename, opts); 149 | 150 | tsLoader = (filepath) => jiti.import(filepath, { default: true }); 151 | } 152 | 153 | loaders[".cts"] = tsLoader; 154 | loaders[".mts"] = tsLoader; 155 | loaders[".ts"] = tsLoader; 156 | 157 | const explorer = cosmiconfig(moduleName, { 158 | searchStrategy: "global", 159 | searchPlaces, 160 | loaders, 161 | }); 162 | 163 | const result = await (stats.isFile() 164 | ? explorer.load(searchPath) 165 | : explorer.search(searchPath)); 166 | 167 | if (!result) { 168 | return {}; 169 | } 170 | 171 | loaderContext.addBuildDependency(result.filepath); 172 | loaderContext.addDependency(result.filepath); 173 | 174 | if (result.isEmpty) { 175 | return result; 176 | } 177 | 178 | if (typeof result.config === "function") { 179 | const api = { 180 | mode: loaderContext.mode, 181 | file: loaderContext.resourcePath, 182 | // For complex use 183 | webpackLoaderContext: loaderContext, 184 | // Partial compatibility with `postcss-cli` 185 | env: loaderContext.mode, 186 | options: postcssOptions || {}, 187 | }; 188 | 189 | return { ...result, config: result.config(api) }; 190 | } 191 | 192 | return result; 193 | } 194 | 195 | function loadPlugin(plugin, options, file) { 196 | try { 197 | let loadedPlugin = require(plugin); 198 | 199 | if (loadedPlugin.default) { 200 | loadedPlugin = loadedPlugin.default; 201 | } 202 | 203 | if (!options || Object.keys(options).length === 0) { 204 | return loadedPlugin; 205 | } 206 | 207 | return loadedPlugin(options); 208 | } catch (error) { 209 | throw new Error( 210 | `Loading PostCSS "${plugin}" plugin failed: ${error.message}\n\n(@${file})`, 211 | ); 212 | } 213 | } 214 | 215 | function pluginFactory() { 216 | const listOfPlugins = new Map(); 217 | 218 | return (plugins) => { 219 | if (typeof plugins === "undefined") { 220 | return listOfPlugins; 221 | } 222 | 223 | if (Array.isArray(plugins)) { 224 | for (const plugin of plugins) { 225 | if (Array.isArray(plugin)) { 226 | const [name, options] = plugin; 227 | 228 | listOfPlugins.set(name, options); 229 | } else if (plugin && typeof plugin === "function") { 230 | listOfPlugins.set(plugin); 231 | } else if ( 232 | plugin && 233 | Object.keys(plugin).length === 1 && 234 | (typeof plugin[Object.keys(plugin)[0]] === "object" || 235 | typeof plugin[Object.keys(plugin)[0]] === "boolean") && 236 | plugin[Object.keys(plugin)[0]] !== null 237 | ) { 238 | const [name] = Object.keys(plugin); 239 | const options = plugin[name]; 240 | 241 | if (options === false) { 242 | listOfPlugins.delete(name); 243 | } else { 244 | listOfPlugins.set(name, options); 245 | } 246 | } else if (plugin) { 247 | listOfPlugins.set(plugin); 248 | } 249 | } 250 | } else { 251 | const objectPlugins = Object.entries(plugins); 252 | 253 | for (const [name, options] of objectPlugins) { 254 | if (options === false) { 255 | listOfPlugins.delete(name); 256 | } else { 257 | listOfPlugins.set(name, options); 258 | } 259 | } 260 | } 261 | 262 | return listOfPlugins; 263 | }; 264 | } 265 | 266 | async function tryRequireThenImport(module) { 267 | let exports; 268 | 269 | try { 270 | exports = require(module); 271 | 272 | return exports; 273 | } catch (err) { 274 | let importESM; 275 | 276 | try { 277 | // eslint-disable-next-line no-new-func 278 | importESM = new Function("id", "return import(id);"); 279 | } catch { 280 | importESM = null; 281 | } 282 | 283 | if (err.code === "ERR_REQUIRE_ESM" && importESM) { 284 | exports = await importESM(module); 285 | 286 | return exports.default; 287 | } 288 | 289 | throw err; 290 | } 291 | } 292 | 293 | async function getPostcssOptions( 294 | loaderContext, 295 | loadedConfig = {}, 296 | postcssOptions = {}, 297 | ) { 298 | const file = loaderContext.resourcePath; 299 | 300 | let normalizedPostcssOptions = postcssOptions; 301 | 302 | if (typeof normalizedPostcssOptions === "function") { 303 | normalizedPostcssOptions = normalizedPostcssOptions(loaderContext); 304 | } 305 | 306 | let plugins = []; 307 | 308 | try { 309 | const factory = pluginFactory(); 310 | 311 | if (loadedConfig.config && loadedConfig.config.plugins) { 312 | factory(loadedConfig.config.plugins); 313 | } 314 | 315 | factory(normalizedPostcssOptions.plugins); 316 | 317 | plugins = [...factory()].map((item) => { 318 | const [plugin, options] = item; 319 | 320 | if (typeof plugin === "string") { 321 | return loadPlugin(plugin, options, file); 322 | } 323 | 324 | return plugin; 325 | }); 326 | } catch (error) { 327 | loaderContext.emitError(error); 328 | } 329 | 330 | const processOptionsFromConfig = { ...loadedConfig.config }; 331 | 332 | if (processOptionsFromConfig.from) { 333 | processOptionsFromConfig.from = path.resolve( 334 | path.dirname(loadedConfig.filepath), 335 | processOptionsFromConfig.from, 336 | ); 337 | } 338 | 339 | if (processOptionsFromConfig.to) { 340 | processOptionsFromConfig.to = path.resolve( 341 | path.dirname(loadedConfig.filepath), 342 | processOptionsFromConfig.to, 343 | ); 344 | } 345 | 346 | const processOptionsFromOptions = { ...normalizedPostcssOptions }; 347 | 348 | if (processOptionsFromOptions.from) { 349 | processOptionsFromOptions.from = path.resolve( 350 | loaderContext.rootContext, 351 | processOptionsFromOptions.from, 352 | ); 353 | } 354 | 355 | if (processOptionsFromOptions.to) { 356 | processOptionsFromOptions.to = path.resolve( 357 | loaderContext.rootContext, 358 | processOptionsFromOptions.to, 359 | ); 360 | } 361 | 362 | // No need `plugins` and `config` for processOptions 363 | const { plugins: __plugins, ...optionsFromConfig } = processOptionsFromConfig; 364 | const { 365 | config: _config, 366 | plugins: _plugins, 367 | ...optionsFromOptions 368 | } = processOptionsFromOptions; 369 | 370 | const processOptions = { 371 | from: file, 372 | to: file, 373 | map: false, 374 | ...optionsFromConfig, 375 | ...optionsFromOptions, 376 | }; 377 | 378 | if (typeof processOptions.parser === "string") { 379 | try { 380 | processOptions.parser = await tryRequireThenImport(processOptions.parser); 381 | } catch (error) { 382 | loaderContext.emitError( 383 | new Error( 384 | `Loading PostCSS "${processOptions.parser}" parser failed: ${error.message}\n\n(@${file})`, 385 | ), 386 | ); 387 | } 388 | } 389 | 390 | if (typeof processOptions.stringifier === "string") { 391 | try { 392 | processOptions.stringifier = await tryRequireThenImport( 393 | processOptions.stringifier, 394 | ); 395 | } catch (error) { 396 | loaderContext.emitError( 397 | new Error( 398 | `Loading PostCSS "${processOptions.stringifier}" stringifier failed: ${error.message}\n\n(@${file})`, 399 | ), 400 | ); 401 | } 402 | } 403 | 404 | if (typeof processOptions.syntax === "string") { 405 | try { 406 | processOptions.syntax = await tryRequireThenImport(processOptions.syntax); 407 | } catch (error) { 408 | loaderContext.emitError( 409 | new Error( 410 | `Loading PostCSS "${processOptions.syntax}" syntax failed: ${error.message}\n\n(@${file})`, 411 | ), 412 | ); 413 | } 414 | } 415 | 416 | if (processOptions.map === true) { 417 | // https://github.com/postcss/postcss/blob/main/docs/source-maps.md 418 | processOptions.map = { inline: true }; 419 | } 420 | 421 | return { plugins, processOptions }; 422 | } 423 | 424 | const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i; 425 | const ABSOLUTE_SCHEME = /^[a-z0-9+\-.]+:/i; 426 | 427 | function getURLType(source) { 428 | if (source[0] === "/") { 429 | if (source[1] === "/") { 430 | return "scheme-relative"; 431 | } 432 | 433 | return "path-absolute"; 434 | } 435 | 436 | if (IS_NATIVE_WIN32_PATH.test(source)) { 437 | return "path-absolute"; 438 | } 439 | 440 | return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative"; 441 | } 442 | 443 | function normalizeSourceMap(map, resourceContext) { 444 | let newMap = map; 445 | 446 | // Some loader emit source map as string 447 | // Strip any JSON XSSI avoidance prefix from the string (as documented in the source maps specification), and then parse the string as JSON. 448 | if (typeof newMap === "string") { 449 | newMap = JSON.parse(newMap); 450 | } 451 | 452 | delete newMap.file; 453 | 454 | const { sourceRoot } = newMap; 455 | 456 | delete newMap.sourceRoot; 457 | 458 | if (newMap.sources) { 459 | newMap.sources = newMap.sources.map((source) => { 460 | const sourceType = getURLType(source); 461 | 462 | // Do no touch `scheme-relative` and `absolute` URLs 463 | if (sourceType === "path-relative" || sourceType === "path-absolute") { 464 | const absoluteSource = 465 | sourceType === "path-relative" && sourceRoot 466 | ? path.resolve(sourceRoot, path.normalize(source)) 467 | : path.normalize(source); 468 | 469 | return path.relative(resourceContext, absoluteSource); 470 | } 471 | 472 | return source; 473 | }); 474 | } 475 | 476 | return newMap; 477 | } 478 | 479 | function normalizeSourceMapAfterPostcss(map, resourceContext) { 480 | const newMap = map; 481 | 482 | // result.map.file is an optional property that provides the output filename. 483 | // Since we don't know the final filename in the webpack build chain yet, it makes no sense to have it. 484 | 485 | delete newMap.file; 486 | 487 | newMap.sourceRoot = ""; 488 | 489 | newMap.sources = newMap.sources.map((source) => { 490 | if (source.indexOf("<") === 0) { 491 | return source; 492 | } 493 | 494 | const sourceType = getURLType(source); 495 | 496 | // Do no touch `scheme-relative`, `path-absolute` and `absolute` types 497 | if (sourceType === "path-relative") { 498 | return path.resolve(resourceContext, source); 499 | } 500 | 501 | return source; 502 | }); 503 | 504 | return newMap; 505 | } 506 | 507 | function findPackageJSONDir(cwd, statSync) { 508 | let dir = cwd; 509 | 510 | for (;;) { 511 | try { 512 | if (statSync(path.join(dir, "package.json")).isFile()) { 513 | break; 514 | } 515 | } catch { 516 | // Nothing 517 | } 518 | 519 | const parent = path.dirname(dir); 520 | 521 | if (dir === parent) { 522 | dir = null; 523 | break; 524 | } 525 | 526 | dir = parent; 527 | } 528 | 529 | return dir; 530 | } 531 | 532 | function getPostcssImplementation(loaderContext, implementation) { 533 | let resolvedImplementation = implementation; 534 | 535 | if (!implementation || typeof implementation === "string") { 536 | const postcssImplPkg = implementation || "postcss"; 537 | 538 | resolvedImplementation = require(postcssImplPkg); 539 | } 540 | 541 | return resolvedImplementation; 542 | } 543 | 544 | function syntaxErrorFactory(error) { 545 | let message = "\nSyntaxError\n\n"; 546 | 547 | if (typeof error.line !== "undefined") { 548 | message += `(${error.line}:${error.column}) `; 549 | } 550 | 551 | if (typeof error.plugin !== "undefined") { 552 | message += `from "${error.plugin}" plugin: `; 553 | } 554 | 555 | message += error.file ? `${error.file} ` : " "; 556 | message += `${error.reason}`; 557 | 558 | const code = error.showSourceCode(); 559 | 560 | if (code) { 561 | message += `\n\n${code}\n`; 562 | } 563 | 564 | const obj = new Error(message, { cause: error }); 565 | 566 | obj.stack = null; 567 | 568 | return obj; 569 | } 570 | 571 | function reportError(loaderContext, callback, error) { 572 | if (error.file) { 573 | loaderContext.addDependency(error.file); 574 | } 575 | 576 | if (error.name === "CssSyntaxError") { 577 | callback(syntaxErrorFactory(error)); 578 | } else { 579 | callback(error); 580 | } 581 | } 582 | 583 | function warningFactory(warning) { 584 | let message = ""; 585 | 586 | if (typeof warning.line !== "undefined") { 587 | message += `(${warning.line}:${warning.column}) `; 588 | } 589 | 590 | if (typeof warning.plugin !== "undefined") { 591 | message += `from "${warning.plugin}" plugin: `; 592 | } 593 | 594 | message += warning.text; 595 | 596 | if (warning.node) { 597 | message += `\n\nCode:\n ${warning.node.toString()}\n`; 598 | } 599 | 600 | const obj = new Error(message, { cause: warning }); 601 | 602 | obj.stack = null; 603 | 604 | return obj; 605 | } 606 | 607 | export { 608 | exec, 609 | findPackageJSONDir, 610 | getPostcssImplementation, 611 | getPostcssOptions, 612 | loadConfig, 613 | normalizeSourceMap, 614 | normalizeSourceMapAfterPostcss, 615 | reportError, 616 | warningFactory, 617 | }; 618 | -------------------------------------------------------------------------------- /test/sourceMap.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment node 3 | */ 4 | 5 | import fs from "node:fs"; 6 | import path from "node:path"; 7 | 8 | import { 9 | compile, 10 | getCodeFromBundle, 11 | getCompiler, 12 | getErrors, 13 | getWarnings, 14 | } from "./helpers"; 15 | 16 | describe('"sourceMap" option', () => { 17 | it('should generate source maps with "true" value and the "devtool" with "false" value', async () => { 18 | const compiler = getCompiler( 19 | "./css/index.js", 20 | { 21 | sourceMap: true, 22 | postcssOptions: { hideNothingWarning: true }, 23 | }, 24 | { 25 | devtool: false, 26 | }, 27 | ); 28 | const stats = await compile(compiler); 29 | const { css, sourceMap } = getCodeFromBundle("style.css", stats); 30 | 31 | sourceMap.sourceRoot = ""; 32 | sourceMap.sources = sourceMap.sources.map((source) => { 33 | expect(path.isAbsolute(source)).toBe(true); 34 | expect(source).toBe(path.normalize(source)); 35 | expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( 36 | true, 37 | ); 38 | 39 | return path 40 | .relative(path.resolve(__dirname, "./fixtures"), source) 41 | .replaceAll("\\", "/"); 42 | }); 43 | 44 | expect(css).toMatchSnapshot("css"); 45 | expect(sourceMap).toMatchSnapshot("source map"); 46 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 47 | expect(getErrors(stats)).toMatchSnapshot("errors"); 48 | }); 49 | 50 | it('should generate source maps with "true" value and the "devtool" option with "source-map" value', async () => { 51 | const compiler = getCompiler( 52 | "./css/index.js", 53 | { 54 | sourceMap: true, 55 | postcssOptions: { hideNothingWarning: true }, 56 | }, 57 | { 58 | devtool: "source-map", 59 | }, 60 | ); 61 | const stats = await compile(compiler); 62 | const { css, sourceMap } = getCodeFromBundle("style.css", stats); 63 | 64 | sourceMap.sourceRoot = ""; 65 | sourceMap.sources = sourceMap.sources.map((source) => { 66 | expect(path.isAbsolute(source)).toBe(true); 67 | expect(source).toBe(path.normalize(source)); 68 | expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( 69 | true, 70 | ); 71 | 72 | return path 73 | .relative(path.resolve(__dirname, "./fixtures"), source) 74 | .replaceAll("\\", "/"); 75 | }); 76 | 77 | expect(css).toMatchSnapshot("css"); 78 | expect(sourceMap).toMatchSnapshot("source map"); 79 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 80 | expect(getErrors(stats)).toMatchSnapshot("errors"); 81 | }); 82 | 83 | it('should generate source maps when value is not specified and the "devtool" with "source-map" value', async () => { 84 | const compiler = getCompiler( 85 | "./css/index.js", 86 | { 87 | postcssOptions: { hideNothingWarning: true }, 88 | }, 89 | { 90 | devtool: "source-map", 91 | }, 92 | ); 93 | const stats = await compile(compiler); 94 | const { css, sourceMap } = getCodeFromBundle("style.css", stats); 95 | 96 | sourceMap.sourceRoot = ""; 97 | sourceMap.sources = sourceMap.sources.map((source) => { 98 | expect(path.isAbsolute(source)).toBe(true); 99 | expect(source).toBe(path.normalize(source)); 100 | expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( 101 | true, 102 | ); 103 | 104 | return path 105 | .relative(path.resolve(__dirname, "./fixtures"), source) 106 | .replaceAll("\\", "/"); 107 | }); 108 | 109 | expect(css).toMatchSnapshot("css"); 110 | expect(sourceMap).toMatchSnapshot("source map"); 111 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 112 | expect(getErrors(stats)).toMatchSnapshot("errors"); 113 | }); 114 | 115 | it('should generate source maps with "false" value, but the "postcssOptions.map" has values', async () => { 116 | const compiler = getCompiler( 117 | "./css/index.js", 118 | { 119 | postcssOptions: { 120 | map: { 121 | inline: false, 122 | annotation: false, 123 | prev: false, 124 | sourcesContent: true, 125 | }, 126 | hideNothingWarning: true, 127 | }, 128 | }, 129 | { 130 | devtool: false, 131 | }, 132 | ); 133 | const stats = await compile(compiler); 134 | const { css, sourceMap } = getCodeFromBundle("style.css", stats); 135 | 136 | sourceMap.sourceRoot = ""; 137 | sourceMap.sources = sourceMap.sources.map((source) => { 138 | expect(path.isAbsolute(source)).toBe(false); 139 | expect(source).toBe(path.normalize(source)); 140 | expect( 141 | fs.existsSync(path.resolve(__dirname, "./fixtures/css", source)), 142 | ).toBe(true); 143 | 144 | return source.replaceAll("\\", "/"); 145 | }); 146 | 147 | expect(css).toMatchSnapshot("css"); 148 | expect(sourceMap).toMatchSnapshot("source map"); 149 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 150 | expect(getErrors(stats)).toMatchSnapshot("errors"); 151 | }); 152 | 153 | it('should generate source maps using the "postcssOptions.map" option with "true" value and previous loader returns source maps ("sass-loader")', async () => { 154 | const compiler = getCompiler( 155 | "./scss/index.js", 156 | {}, 157 | { 158 | devtool: false, 159 | module: { 160 | rules: [ 161 | { 162 | test: /\.scss$/i, 163 | use: [ 164 | { 165 | loader: require.resolve("./helpers/testLoader"), 166 | options: {}, 167 | }, 168 | { 169 | loader: path.resolve(__dirname, "../src"), 170 | options: { 171 | postcssOptions: { 172 | map: true, 173 | hideNothingWarning: true, 174 | }, 175 | }, 176 | }, 177 | { 178 | loader: "sass-loader", 179 | options: { 180 | implementation: require("sass"), 181 | sassOptions: { 182 | sourceMap: true, 183 | sourceMapRoot: path.resolve( 184 | __dirname, 185 | "./fixtures/scss/", 186 | ), 187 | outFile: path.resolve( 188 | __dirname, 189 | "./fixtures/scss/style.css.map", 190 | ), 191 | sourceMapContents: true, 192 | omitSourceMapUrl: true, 193 | sourceMapEmbed: false, 194 | }, 195 | }, 196 | }, 197 | ], 198 | }, 199 | ], 200 | }, 201 | }, 202 | ); 203 | const stats = await compile(compiler); 204 | const { css, sourceMap } = getCodeFromBundle("style.scss", stats); 205 | 206 | expect(css).toMatchSnapshot("css"); 207 | expect(sourceMap).toBeUndefined(); 208 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 209 | expect(getErrors(stats)).toMatchSnapshot("errors"); 210 | }); 211 | 212 | it('should generate source maps using the "postcssOptions.map" option with values and previous loader returns source maps ("sass-loader")', async () => { 213 | const compiler = getCompiler( 214 | "./scss/index.js", 215 | {}, 216 | { 217 | devtool: false, 218 | module: { 219 | rules: [ 220 | { 221 | test: /\.scss$/i, 222 | use: [ 223 | { 224 | loader: require.resolve("./helpers/testLoader"), 225 | options: {}, 226 | }, 227 | { 228 | loader: path.resolve(__dirname, "../src"), 229 | options: { 230 | postcssOptions: { 231 | map: { 232 | inline: false, 233 | sourcesContent: true, 234 | annotation: true, 235 | }, 236 | hideNothingWarning: true, 237 | }, 238 | }, 239 | }, 240 | { 241 | loader: "sass-loader", 242 | options: { 243 | implementation: require("sass"), 244 | sassOptions: { 245 | sourceMap: true, 246 | sourceMapRoot: path.resolve( 247 | __dirname, 248 | "./fixtures/scss/", 249 | ), 250 | outFile: path.resolve( 251 | __dirname, 252 | "./fixtures/scss/style.css.map", 253 | ), 254 | sourceMapContents: true, 255 | omitSourceMapUrl: true, 256 | sourceMapEmbed: false, 257 | }, 258 | }, 259 | }, 260 | ], 261 | }, 262 | ], 263 | }, 264 | }, 265 | ); 266 | const stats = await compile(compiler); 267 | const { css, sourceMap } = getCodeFromBundle("style.scss", stats); 268 | 269 | sourceMap.sourceRoot = ""; 270 | sourceMap.sources = sourceMap.sources.map((source) => { 271 | expect(path.isAbsolute(source)).toBe(false); 272 | expect( 273 | fs.existsSync(path.resolve(__dirname, "./fixtures/scss", source)), 274 | ).toBe(true); 275 | 276 | return source.replaceAll("\\", "/"); 277 | }); 278 | 279 | expect(css).toMatchSnapshot("css"); 280 | expect(sourceMap).toMatchSnapshot("source map"); 281 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 282 | expect(getErrors(stats)).toMatchSnapshot("errors"); 283 | }); 284 | 285 | it('should not generate source maps with "false" value and the "devtool" option with "false" value', async () => { 286 | const compiler = getCompiler( 287 | "./css/index.js", 288 | { 289 | sourceMap: false, 290 | postcssOptions: { hideNothingWarning: true }, 291 | }, 292 | { 293 | devtool: false, 294 | }, 295 | ); 296 | const stats = await compile(compiler); 297 | const { css, sourceMap } = getCodeFromBundle("style.css", stats); 298 | 299 | expect(css).toMatchSnapshot("css"); 300 | expect(sourceMap).toBeUndefined(); 301 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 302 | expect(getErrors(stats)).toMatchSnapshot("errors"); 303 | }); 304 | 305 | it('should not generate source maps with "false" value and the "devtool" option with "source-map" value', async () => { 306 | const compiler = getCompiler( 307 | "./css/index.js", 308 | { 309 | sourceMap: false, 310 | postcssOptions: { hideNothingWarning: true }, 311 | }, 312 | { 313 | devtool: "source-map", 314 | }, 315 | ); 316 | const stats = await compile(compiler); 317 | const { css, sourceMap } = getCodeFromBundle("style.css", stats); 318 | 319 | expect(css).toMatchSnapshot("css"); 320 | expect(sourceMap).toBeUndefined(); 321 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 322 | expect(getErrors(stats)).toMatchSnapshot("errors"); 323 | }); 324 | 325 | it('should not generate source maps when value is not specified and the "devtool" option with "source-map" value', async () => { 326 | const compiler = getCompiler( 327 | "./css/index.js", 328 | { 329 | postcssOptions: { hideNothingWarning: true }, 330 | }, 331 | { 332 | devtool: false, 333 | }, 334 | ); 335 | const stats = await compile(compiler); 336 | const { css, sourceMap } = getCodeFromBundle("style.css", stats); 337 | 338 | expect(css).toMatchSnapshot("css"); 339 | expect(sourceMap).toBeUndefined(); 340 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 341 | expect(getErrors(stats)).toMatchSnapshot("errors"); 342 | }); 343 | 344 | it('should generate source maps when previous loader returns source maps ("sass-loader")', async () => { 345 | const compiler = getCompiler( 346 | "./scss/index.js", 347 | {}, 348 | { 349 | devtool: "source-map", 350 | module: { 351 | rules: [ 352 | { 353 | test: /\.scss$/i, 354 | use: [ 355 | { 356 | loader: require.resolve("./helpers/testLoader"), 357 | }, 358 | { 359 | loader: path.resolve(__dirname, "../src"), 360 | options: { 361 | postcssOptions: { hideNothingWarning: true }, 362 | }, 363 | }, 364 | { 365 | loader: "sass-loader", 366 | options: { 367 | implementation: require("sass"), 368 | }, 369 | }, 370 | ], 371 | }, 372 | ], 373 | }, 374 | }, 375 | ); 376 | const stats = await compile(compiler); 377 | const { css, sourceMap } = getCodeFromBundle("style.scss", stats); 378 | 379 | sourceMap.sourceRoot = ""; 380 | sourceMap.sources = sourceMap.sources.map((source) => { 381 | expect(path.isAbsolute(source)).toBe(true); 382 | expect(source).toBe(path.normalize(source)); 383 | expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( 384 | true, 385 | ); 386 | 387 | return path 388 | .relative(path.resolve(__dirname, "./fixtures"), source) 389 | .replaceAll("\\", "/"); 390 | }); 391 | 392 | expect(css).toMatchSnapshot("css"); 393 | expect(sourceMap).toMatchSnapshot("source map"); 394 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 395 | expect(getErrors(stats)).toMatchSnapshot("errors"); 396 | }); 397 | 398 | it('should generate source maps when previous loader returns source maps ("less-loader")', async () => { 399 | const compiler = getCompiler( 400 | "./less/index.js", 401 | {}, 402 | { 403 | devtool: "source-map", 404 | module: { 405 | rules: [ 406 | { 407 | test: /\.less$/i, 408 | use: [ 409 | { 410 | loader: require.resolve("./helpers/testLoader"), 411 | options: {}, 412 | }, 413 | { 414 | loader: path.resolve(__dirname, "../src"), 415 | options: { 416 | postcssOptions: { hideNothingWarning: true }, 417 | }, 418 | }, 419 | { 420 | loader: "less-loader", 421 | }, 422 | ], 423 | }, 424 | ], 425 | }, 426 | }, 427 | ); 428 | const stats = await compile(compiler); 429 | const { css, sourceMap } = getCodeFromBundle("style.less", stats); 430 | 431 | sourceMap.sourceRoot = ""; 432 | sourceMap.sources = sourceMap.sources.map((source) => { 433 | expect(path.isAbsolute(source)).toBe(true); 434 | expect(source).toBe(path.normalize(source)); 435 | expect(fs.existsSync(path.resolve(sourceMap.sourceRoot, source))).toBe( 436 | true, 437 | ); 438 | 439 | return path 440 | .relative(path.resolve(__dirname, "./fixtures"), source) 441 | .replaceAll("\\", "/"); 442 | }); 443 | 444 | expect(css).toMatchSnapshot("css"); 445 | expect(sourceMap).toMatchSnapshot("source map"); 446 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 447 | expect(getErrors(stats)).toMatchSnapshot("errors"); 448 | }); 449 | 450 | it('should generate inline source maps when the "devtool" is "false"', async () => { 451 | const compiler = getCompiler( 452 | "./css/index.js", 453 | { 454 | postcssOptions: { 455 | map: { 456 | inline: true, 457 | annotation: false, 458 | }, 459 | hideNothingWarning: true, 460 | }, 461 | }, 462 | { 463 | devtool: false, 464 | }, 465 | ); 466 | const stats = await compile(compiler); 467 | const { css } = getCodeFromBundle("style.css", stats); 468 | 469 | expect(css).toMatchSnapshot("css"); 470 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 471 | expect(getErrors(stats)).toMatchSnapshot("errors"); 472 | }); 473 | 474 | it('should generate inline source maps when the "devtool" is "true"', async () => { 475 | const compiler = getCompiler( 476 | "./css/index.js", 477 | { 478 | postcssOptions: { 479 | map: { 480 | inline: true, 481 | annotation: false, 482 | }, 483 | hideNothingWarning: true, 484 | }, 485 | }, 486 | { 487 | devtool: "source-map", 488 | }, 489 | ); 490 | const stats = await compile(compiler); 491 | const { css } = getCodeFromBundle("style.css", stats); 492 | 493 | expect(css).toMatchSnapshot("css"); 494 | expect(getWarnings(stats)).toMatchSnapshot("warnings"); 495 | expect(getErrors(stats)).toMatchSnapshot("errors"); 496 | }); 497 | }); 498 | -------------------------------------------------------------------------------- /test/__snapshots__/sourceMap.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing 2 | 3 | exports[`"sourceMap" option should generate inline source maps when the "devtool" is "false": css 1`] = ` 4 | "a { 5 | color: black; 6 | } 7 | 8 | a { 9 | color: red; 10 | } 11 | 12 | a { 13 | color: green; 14 | } 15 | 16 | a { 17 | color: blue; 18 | } 19 | 20 | .class { 21 | -x-border-color: blue blue *; 22 | -x-color: * #fafafa; 23 | } 24 | 25 | .class-foo { 26 | -z-border-color: blue blue *; 27 | -z-color: * #fafafa; 28 | } 29 | 30 | .phone { 31 | &_title { 32 | width: 500px; 33 | 34 | @media (max-width: 500px) { 35 | width: auto; 36 | } 37 | 38 | body.is_dark & { 39 | color: white; 40 | } 41 | } 42 | 43 | img { 44 | display: block; 45 | } 46 | } 47 | 48 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSIsImZpbGUiOiJzdHlsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyJhIHtcbiAgY29sb3I6IGJsYWNrO1xufVxuXG5hIHtcbiAgY29sb3I6IHJlZDtcbn1cblxuYSB7XG4gIGNvbG9yOiBncmVlbjtcbn1cblxuYSB7XG4gIGNvbG9yOiBibHVlO1xufVxuXG4uY2xhc3Mge1xuICAteC1ib3JkZXItY29sb3I6IGJsdWUgYmx1ZSAqO1xuICAteC1jb2xvcjogKiAjZmFmYWZhO1xufVxuXG4uY2xhc3MtZm9vIHtcbiAgLXotYm9yZGVyLWNvbG9yOiBibHVlIGJsdWUgKjtcbiAgLXotY29sb3I6ICogI2ZhZmFmYTtcbn1cblxuLnBob25lIHtcbiAgJl90aXRsZSB7XG4gICAgd2lkdGg6IDUwMHB4O1xuXG4gICAgQG1lZGlhIChtYXgtd2lkdGg6IDUwMHB4KSB7XG4gICAgICB3aWR0aDogYXV0bztcbiAgICB9XG5cbiAgICBib2R5LmlzX2RhcmsgJiB7XG4gICAgICBjb2xvcjogd2hpdGU7XG4gICAgfVxuICB9XG5cbiAgaW1nIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgfVxufVxuIl19 */" 49 | `; 50 | 51 | exports[`"sourceMap" option should generate inline source maps when the "devtool" is "false": errors 1`] = `[]`; 52 | 53 | exports[`"sourceMap" option should generate inline source maps when the "devtool" is "false": warnings 1`] = `[]`; 54 | 55 | exports[`"sourceMap" option should generate inline source maps when the "devtool" is "true": css 1`] = ` 56 | "a { 57 | color: black; 58 | } 59 | 60 | a { 61 | color: red; 62 | } 63 | 64 | a { 65 | color: green; 66 | } 67 | 68 | a { 69 | color: blue; 70 | } 71 | 72 | .class { 73 | -x-border-color: blue blue *; 74 | -x-color: * #fafafa; 75 | } 76 | 77 | .class-foo { 78 | -z-border-color: blue blue *; 79 | -z-color: * #fafafa; 80 | } 81 | 82 | .phone { 83 | &_title { 84 | width: 500px; 85 | 86 | @media (max-width: 500px) { 87 | width: auto; 88 | } 89 | 90 | body.is_dark & { 91 | color: white; 92 | } 93 | } 94 | 95 | img { 96 | display: block; 97 | } 98 | } 99 | 100 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSIsImZpbGUiOiJzdHlsZS5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyJhIHtcbiAgY29sb3I6IGJsYWNrO1xufVxuXG5hIHtcbiAgY29sb3I6IHJlZDtcbn1cblxuYSB7XG4gIGNvbG9yOiBncmVlbjtcbn1cblxuYSB7XG4gIGNvbG9yOiBibHVlO1xufVxuXG4uY2xhc3Mge1xuICAteC1ib3JkZXItY29sb3I6IGJsdWUgYmx1ZSAqO1xuICAteC1jb2xvcjogKiAjZmFmYWZhO1xufVxuXG4uY2xhc3MtZm9vIHtcbiAgLXotYm9yZGVyLWNvbG9yOiBibHVlIGJsdWUgKjtcbiAgLXotY29sb3I6ICogI2ZhZmFmYTtcbn1cblxuLnBob25lIHtcbiAgJl90aXRsZSB7XG4gICAgd2lkdGg6IDUwMHB4O1xuXG4gICAgQG1lZGlhIChtYXgtd2lkdGg6IDUwMHB4KSB7XG4gICAgICB3aWR0aDogYXV0bztcbiAgICB9XG5cbiAgICBib2R5LmlzX2RhcmsgJiB7XG4gICAgICBjb2xvcjogd2hpdGU7XG4gICAgfVxuICB9XG5cbiAgaW1nIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgfVxufVxuIl19 */" 101 | `; 102 | 103 | exports[`"sourceMap" option should generate inline source maps when the "devtool" is "true": errors 1`] = `[]`; 104 | 105 | exports[`"sourceMap" option should generate inline source maps when the "devtool" is "true": warnings 1`] = `[]`; 106 | 107 | exports[`"sourceMap" option should generate source maps using the "postcssOptions.map" option with "true" value and previous loader returns source maps ("sass-loader"): css 1`] = ` 108 | "a { 109 | color: coral; 110 | } 111 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlLnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFBSSIsImZpbGUiOiJzdHlsZS5zY3NzIiwic291cmNlc0NvbnRlbnQiOlsiYSB7XG4gIGNvbG9yOiBjb3JhbDtcbn0iXX0= */" 112 | `; 113 | 114 | exports[`"sourceMap" option should generate source maps using the "postcssOptions.map" option with "true" value and previous loader returns source maps ("sass-loader"): errors 1`] = `[]`; 115 | 116 | exports[`"sourceMap" option should generate source maps using the "postcssOptions.map" option with "true" value and previous loader returns source maps ("sass-loader"): warnings 1`] = ` 117 | [ 118 | "ModuleWarning: Module Warning (from \`replaced original path\`): 119 | Deprecation The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0. 120 | 121 | More info: https://sass-lang.com/d/legacy-js-api", 122 | ] 123 | `; 124 | 125 | exports[`"sourceMap" option should generate source maps using the "postcssOptions.map" option with values and previous loader returns source maps ("sass-loader"): css 1`] = ` 126 | "a { 127 | color: coral; 128 | } 129 | /*# sourceMappingURL=style.scss.map */" 130 | `; 131 | 132 | exports[`"sourceMap" option should generate source maps using the "postcssOptions.map" option with values and previous loader returns source maps ("sass-loader"): errors 1`] = `[]`; 133 | 134 | exports[`"sourceMap" option should generate source maps using the "postcssOptions.map" option with values and previous loader returns source maps ("sass-loader"): source map 1`] = ` 135 | { 136 | "file": "style.scss", 137 | "mappings": "AAAA;EAAI", 138 | "names": [], 139 | "sourceRoot": "", 140 | "sources": [ 141 | "style.scss", 142 | ], 143 | "sourcesContent": [ 144 | "a { 145 | color: coral; 146 | }", 147 | ], 148 | "version": 3, 149 | } 150 | `; 151 | 152 | exports[`"sourceMap" option should generate source maps using the "postcssOptions.map" option with values and previous loader returns source maps ("sass-loader"): warnings 1`] = ` 153 | [ 154 | "ModuleWarning: Module Warning (from \`replaced original path\`): 155 | Deprecation The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0. 156 | 157 | More info: https://sass-lang.com/d/legacy-js-api", 158 | ] 159 | `; 160 | 161 | exports[`"sourceMap" option should generate source maps when previous loader returns source maps ("less-loader"): css 1`] = ` 162 | "a { 163 | color: coral; 164 | } 165 | " 166 | `; 167 | 168 | exports[`"sourceMap" option should generate source maps when previous loader returns source maps ("less-loader"): errors 1`] = `[]`; 169 | 170 | exports[`"sourceMap" option should generate source maps when previous loader returns source maps ("less-loader"): source map 1`] = ` 171 | { 172 | "mappings": "AAAA;EAAI,YAAA", 173 | "names": [], 174 | "sourceRoot": "", 175 | "sources": [ 176 | "less/style.less", 177 | ], 178 | "sourcesContent": [ 179 | "a { 180 | color: coral; 181 | } 182 | ", 183 | ], 184 | "version": 3, 185 | } 186 | `; 187 | 188 | exports[`"sourceMap" option should generate source maps when previous loader returns source maps ("less-loader"): warnings 1`] = `[]`; 189 | 190 | exports[`"sourceMap" option should generate source maps when previous loader returns source maps ("sass-loader"): css 1`] = ` 191 | "a { 192 | color: coral; 193 | }" 194 | `; 195 | 196 | exports[`"sourceMap" option should generate source maps when previous loader returns source maps ("sass-loader"): errors 1`] = `[]`; 197 | 198 | exports[`"sourceMap" option should generate source maps when previous loader returns source maps ("sass-loader"): source map 1`] = ` 199 | { 200 | "mappings": "AAAA;EAAI", 201 | "names": [], 202 | "sourceRoot": "", 203 | "sources": [ 204 | "scss/style.scss", 205 | ], 206 | "sourcesContent": [ 207 | "a { 208 | color: coral; 209 | }", 210 | ], 211 | "version": 3, 212 | } 213 | `; 214 | 215 | exports[`"sourceMap" option should generate source maps when previous loader returns source maps ("sass-loader"): warnings 1`] = ` 216 | [ 217 | "ModuleWarning: Module Warning (from \`replaced original path\`): 218 | Deprecation The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0. 219 | 220 | More info: https://sass-lang.com/d/legacy-js-api", 221 | ] 222 | `; 223 | 224 | exports[`"sourceMap" option should generate source maps when value is not specified and the "devtool" with "source-map" value: css 1`] = ` 225 | "a { 226 | color: black; 227 | } 228 | 229 | a { 230 | color: red; 231 | } 232 | 233 | a { 234 | color: green; 235 | } 236 | 237 | a { 238 | color: blue; 239 | } 240 | 241 | .class { 242 | -x-border-color: blue blue *; 243 | -x-color: * #fafafa; 244 | } 245 | 246 | .class-foo { 247 | -z-border-color: blue blue *; 248 | -z-color: * #fafafa; 249 | } 250 | 251 | .phone { 252 | &_title { 253 | width: 500px; 254 | 255 | @media (max-width: 500px) { 256 | width: auto; 257 | } 258 | 259 | body.is_dark & { 260 | color: white; 261 | } 262 | } 263 | 264 | img { 265 | display: block; 266 | } 267 | } 268 | " 269 | `; 270 | 271 | exports[`"sourceMap" option should generate source maps when value is not specified and the "devtool" with "source-map" value: errors 1`] = `[]`; 272 | 273 | exports[`"sourceMap" option should generate source maps when value is not specified and the "devtool" with "source-map" value: source map 1`] = ` 274 | { 275 | "mappings": "AAAA", 276 | "names": [], 277 | "sourceRoot": "", 278 | "sources": [ 279 | "css/style.css", 280 | ], 281 | "sourcesContent": [ 282 | "a { 283 | color: black; 284 | } 285 | 286 | a { 287 | color: red; 288 | } 289 | 290 | a { 291 | color: green; 292 | } 293 | 294 | a { 295 | color: blue; 296 | } 297 | 298 | .class { 299 | -x-border-color: blue blue *; 300 | -x-color: * #fafafa; 301 | } 302 | 303 | .class-foo { 304 | -z-border-color: blue blue *; 305 | -z-color: * #fafafa; 306 | } 307 | 308 | .phone { 309 | &_title { 310 | width: 500px; 311 | 312 | @media (max-width: 500px) { 313 | width: auto; 314 | } 315 | 316 | body.is_dark & { 317 | color: white; 318 | } 319 | } 320 | 321 | img { 322 | display: block; 323 | } 324 | } 325 | ", 326 | ], 327 | "version": 3, 328 | } 329 | `; 330 | 331 | exports[`"sourceMap" option should generate source maps when value is not specified and the "devtool" with "source-map" value: warnings 1`] = `[]`; 332 | 333 | exports[`"sourceMap" option should generate source maps with "false" value, but the "postcssOptions.map" has values: css 1`] = ` 334 | "a { 335 | color: black; 336 | } 337 | 338 | a { 339 | color: red; 340 | } 341 | 342 | a { 343 | color: green; 344 | } 345 | 346 | a { 347 | color: blue; 348 | } 349 | 350 | .class { 351 | -x-border-color: blue blue *; 352 | -x-color: * #fafafa; 353 | } 354 | 355 | .class-foo { 356 | -z-border-color: blue blue *; 357 | -z-color: * #fafafa; 358 | } 359 | 360 | .phone { 361 | &_title { 362 | width: 500px; 363 | 364 | @media (max-width: 500px) { 365 | width: auto; 366 | } 367 | 368 | body.is_dark & { 369 | color: white; 370 | } 371 | } 372 | 373 | img { 374 | display: block; 375 | } 376 | } 377 | " 378 | `; 379 | 380 | exports[`"sourceMap" option should generate source maps with "false" value, but the "postcssOptions.map" has values: errors 1`] = `[]`; 381 | 382 | exports[`"sourceMap" option should generate source maps with "false" value, but the "postcssOptions.map" has values: source map 1`] = ` 383 | { 384 | "file": "style.css", 385 | "mappings": "AAAA", 386 | "names": [], 387 | "sourceRoot": "", 388 | "sources": [ 389 | "style.css", 390 | ], 391 | "sourcesContent": [ 392 | "a { 393 | color: black; 394 | } 395 | 396 | a { 397 | color: red; 398 | } 399 | 400 | a { 401 | color: green; 402 | } 403 | 404 | a { 405 | color: blue; 406 | } 407 | 408 | .class { 409 | -x-border-color: blue blue *; 410 | -x-color: * #fafafa; 411 | } 412 | 413 | .class-foo { 414 | -z-border-color: blue blue *; 415 | -z-color: * #fafafa; 416 | } 417 | 418 | .phone { 419 | &_title { 420 | width: 500px; 421 | 422 | @media (max-width: 500px) { 423 | width: auto; 424 | } 425 | 426 | body.is_dark & { 427 | color: white; 428 | } 429 | } 430 | 431 | img { 432 | display: block; 433 | } 434 | } 435 | ", 436 | ], 437 | "version": 3, 438 | } 439 | `; 440 | 441 | exports[`"sourceMap" option should generate source maps with "false" value, but the "postcssOptions.map" has values: warnings 1`] = `[]`; 442 | 443 | exports[`"sourceMap" option should generate source maps with "true" value and the "devtool" option with "source-map" value: css 1`] = ` 444 | "a { 445 | color: black; 446 | } 447 | 448 | a { 449 | color: red; 450 | } 451 | 452 | a { 453 | color: green; 454 | } 455 | 456 | a { 457 | color: blue; 458 | } 459 | 460 | .class { 461 | -x-border-color: blue blue *; 462 | -x-color: * #fafafa; 463 | } 464 | 465 | .class-foo { 466 | -z-border-color: blue blue *; 467 | -z-color: * #fafafa; 468 | } 469 | 470 | .phone { 471 | &_title { 472 | width: 500px; 473 | 474 | @media (max-width: 500px) { 475 | width: auto; 476 | } 477 | 478 | body.is_dark & { 479 | color: white; 480 | } 481 | } 482 | 483 | img { 484 | display: block; 485 | } 486 | } 487 | " 488 | `; 489 | 490 | exports[`"sourceMap" option should generate source maps with "true" value and the "devtool" option with "source-map" value: errors 1`] = `[]`; 491 | 492 | exports[`"sourceMap" option should generate source maps with "true" value and the "devtool" option with "source-map" value: source map 1`] = ` 493 | { 494 | "mappings": "AAAA", 495 | "names": [], 496 | "sourceRoot": "", 497 | "sources": [ 498 | "css/style.css", 499 | ], 500 | "sourcesContent": [ 501 | "a { 502 | color: black; 503 | } 504 | 505 | a { 506 | color: red; 507 | } 508 | 509 | a { 510 | color: green; 511 | } 512 | 513 | a { 514 | color: blue; 515 | } 516 | 517 | .class { 518 | -x-border-color: blue blue *; 519 | -x-color: * #fafafa; 520 | } 521 | 522 | .class-foo { 523 | -z-border-color: blue blue *; 524 | -z-color: * #fafafa; 525 | } 526 | 527 | .phone { 528 | &_title { 529 | width: 500px; 530 | 531 | @media (max-width: 500px) { 532 | width: auto; 533 | } 534 | 535 | body.is_dark & { 536 | color: white; 537 | } 538 | } 539 | 540 | img { 541 | display: block; 542 | } 543 | } 544 | ", 545 | ], 546 | "version": 3, 547 | } 548 | `; 549 | 550 | exports[`"sourceMap" option should generate source maps with "true" value and the "devtool" option with "source-map" value: warnings 1`] = `[]`; 551 | 552 | exports[`"sourceMap" option should generate source maps with "true" value and the "devtool" with "false" value: css 1`] = ` 553 | "a { 554 | color: black; 555 | } 556 | 557 | a { 558 | color: red; 559 | } 560 | 561 | a { 562 | color: green; 563 | } 564 | 565 | a { 566 | color: blue; 567 | } 568 | 569 | .class { 570 | -x-border-color: blue blue *; 571 | -x-color: * #fafafa; 572 | } 573 | 574 | .class-foo { 575 | -z-border-color: blue blue *; 576 | -z-color: * #fafafa; 577 | } 578 | 579 | .phone { 580 | &_title { 581 | width: 500px; 582 | 583 | @media (max-width: 500px) { 584 | width: auto; 585 | } 586 | 587 | body.is_dark & { 588 | color: white; 589 | } 590 | } 591 | 592 | img { 593 | display: block; 594 | } 595 | } 596 | " 597 | `; 598 | 599 | exports[`"sourceMap" option should generate source maps with "true" value and the "devtool" with "false" value: errors 1`] = `[]`; 600 | 601 | exports[`"sourceMap" option should generate source maps with "true" value and the "devtool" with "false" value: source map 1`] = ` 602 | { 603 | "mappings": "AAAA", 604 | "names": [], 605 | "sourceRoot": "", 606 | "sources": [ 607 | "css/style.css", 608 | ], 609 | "sourcesContent": [ 610 | "a { 611 | color: black; 612 | } 613 | 614 | a { 615 | color: red; 616 | } 617 | 618 | a { 619 | color: green; 620 | } 621 | 622 | a { 623 | color: blue; 624 | } 625 | 626 | .class { 627 | -x-border-color: blue blue *; 628 | -x-color: * #fafafa; 629 | } 630 | 631 | .class-foo { 632 | -z-border-color: blue blue *; 633 | -z-color: * #fafafa; 634 | } 635 | 636 | .phone { 637 | &_title { 638 | width: 500px; 639 | 640 | @media (max-width: 500px) { 641 | width: auto; 642 | } 643 | 644 | body.is_dark & { 645 | color: white; 646 | } 647 | } 648 | 649 | img { 650 | display: block; 651 | } 652 | } 653 | ", 654 | ], 655 | "version": 3, 656 | } 657 | `; 658 | 659 | exports[`"sourceMap" option should generate source maps with "true" value and the "devtool" with "false" value: warnings 1`] = `[]`; 660 | 661 | exports[`"sourceMap" option should not generate source maps when value is not specified and the "devtool" option with "source-map" value: css 1`] = ` 662 | "a { 663 | color: black; 664 | } 665 | 666 | a { 667 | color: red; 668 | } 669 | 670 | a { 671 | color: green; 672 | } 673 | 674 | a { 675 | color: blue; 676 | } 677 | 678 | .class { 679 | -x-border-color: blue blue *; 680 | -x-color: * #fafafa; 681 | } 682 | 683 | .class-foo { 684 | -z-border-color: blue blue *; 685 | -z-color: * #fafafa; 686 | } 687 | 688 | .phone { 689 | &_title { 690 | width: 500px; 691 | 692 | @media (max-width: 500px) { 693 | width: auto; 694 | } 695 | 696 | body.is_dark & { 697 | color: white; 698 | } 699 | } 700 | 701 | img { 702 | display: block; 703 | } 704 | } 705 | " 706 | `; 707 | 708 | exports[`"sourceMap" option should not generate source maps when value is not specified and the "devtool" option with "source-map" value: errors 1`] = `[]`; 709 | 710 | exports[`"sourceMap" option should not generate source maps when value is not specified and the "devtool" option with "source-map" value: warnings 1`] = `[]`; 711 | 712 | exports[`"sourceMap" option should not generate source maps with "false" value and the "devtool" option with "false" value: css 1`] = ` 713 | "a { 714 | color: black; 715 | } 716 | 717 | a { 718 | color: red; 719 | } 720 | 721 | a { 722 | color: green; 723 | } 724 | 725 | a { 726 | color: blue; 727 | } 728 | 729 | .class { 730 | -x-border-color: blue blue *; 731 | -x-color: * #fafafa; 732 | } 733 | 734 | .class-foo { 735 | -z-border-color: blue blue *; 736 | -z-color: * #fafafa; 737 | } 738 | 739 | .phone { 740 | &_title { 741 | width: 500px; 742 | 743 | @media (max-width: 500px) { 744 | width: auto; 745 | } 746 | 747 | body.is_dark & { 748 | color: white; 749 | } 750 | } 751 | 752 | img { 753 | display: block; 754 | } 755 | } 756 | " 757 | `; 758 | 759 | exports[`"sourceMap" option should not generate source maps with "false" value and the "devtool" option with "false" value: errors 1`] = `[]`; 760 | 761 | exports[`"sourceMap" option should not generate source maps with "false" value and the "devtool" option with "false" value: warnings 1`] = `[]`; 762 | 763 | exports[`"sourceMap" option should not generate source maps with "false" value and the "devtool" option with "source-map" value: css 1`] = ` 764 | "a { 765 | color: black; 766 | } 767 | 768 | a { 769 | color: red; 770 | } 771 | 772 | a { 773 | color: green; 774 | } 775 | 776 | a { 777 | color: blue; 778 | } 779 | 780 | .class { 781 | -x-border-color: blue blue *; 782 | -x-color: * #fafafa; 783 | } 784 | 785 | .class-foo { 786 | -z-border-color: blue blue *; 787 | -z-color: * #fafafa; 788 | } 789 | 790 | .phone { 791 | &_title { 792 | width: 500px; 793 | 794 | @media (max-width: 500px) { 795 | width: auto; 796 | } 797 | 798 | body.is_dark & { 799 | color: white; 800 | } 801 | } 802 | 803 | img { 804 | display: block; 805 | } 806 | } 807 | " 808 | `; 809 | 810 | exports[`"sourceMap" option should not generate source maps with "false" value and the "devtool" option with "source-map" value: errors 1`] = `[]`; 811 | 812 | exports[`"sourceMap" option should not generate source maps with "false" value and the "devtool" option with "source-map" value: warnings 1`] = `[]`; 813 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [8.2.0](https://github.com/webpack-contrib/postcss-loader/compare/v8.1.1...v8.2.0) (2025-09-01) 6 | 7 | 8 | ### Features 9 | 10 | * update `jiti` from v1 to v2 ([9c74974](https://github.com/webpack-contrib/postcss-loader/commit/9c749746df8ddadbac3af60387dc562242350c9a)) 11 | 12 | ### [8.1.1](https://github.com/webpack-contrib/postcss-loader/compare/v8.1.0...v8.1.1) (2024-02-28) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * respect `default` when loading postcss esm configs ([52d8050](https://github.com/webpack-contrib/postcss-loader/commit/52d8050335ca803572e9cc9eae9583c2bb505612)) 18 | 19 | ## [8.1.0](https://github.com/webpack-contrib/postcss-loader/compare/v8.0.0...v8.1.0) (2024-01-30) 20 | 21 | 22 | ### Features 23 | 24 | * add `@rspack/core` as an optional peer dependency ([#679](https://github.com/webpack-contrib/postcss-loader/issues/679)) ([512e4c3](https://github.com/webpack-contrib/postcss-loader/commit/512e4c33a6072534008c0eefc481139f58effbdf)) 25 | 26 | ## [8.0.0](https://github.com/webpack-contrib/postcss-loader/compare/v7.3.4...v8.0.0) (2024-01-16) 27 | 28 | 29 | ### ⚠ BREAKING CHANGES 30 | 31 | * minimum supported Node.js version is `18.12.0` ([#677](https://github.com/webpack-contrib/postcss-loader/issues/677)) ([8dd0315](https://github.com/webpack-contrib/postcss-loader/commit/8dd0315c03e0f608b6a9a4c6a58e9e7a64775e56)) 32 | 33 | ### [7.3.4](https://github.com/webpack-contrib/postcss-loader/compare/v7.3.3...v7.3.4) (2023-12-27) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * do not crash if pkg.(d|devD)ependencies unset ([#667](https://github.com/webpack-contrib/postcss-loader/issues/667)) ([8ef0c7e](https://github.com/webpack-contrib/postcss-loader/commit/8ef0c7e5c6c4eb76073b4ea92306fc399014e462)) 39 | 40 | ### [7.3.3](https://github.com/webpack-contrib/postcss-loader/compare/v7.3.2...v7.3.3) (2023-06-10) 41 | 42 | 43 | ### Bug Fixes 44 | 45 | * **perf:** avoid using `klona` for postcss options ([#658](https://github.com/webpack-contrib/postcss-loader/issues/658)) ([e754c3f](https://github.com/webpack-contrib/postcss-loader/commit/e754c3f8451a09ea633674af90fb3b6b1c073460)) 46 | * bug with loading configurations after updating `cosmiconfig` to version 8.2 ([684d265](https://github.com/webpack-contrib/postcss-loader/commit/684d2654393d225bbbcc443ffc9494ab54fe8112)) 47 | 48 | ### [7.3.2](https://github.com/webpack-contrib/postcss-loader/compare/v7.3.1...v7.3.2) (2023-05-28) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * use `cause` to keep original errors and warnings ([#655](https://github.com/webpack-contrib/postcss-loader/issues/655)) ([e8873f4](https://github.com/webpack-contrib/postcss-loader/commit/e8873f46b4ac3cf94d854d8c20f0d0a444309eb6)) 54 | 55 | ### [7.3.1](https://github.com/webpack-contrib/postcss-loader/compare/v7.3.0...v7.3.1) (2023-05-26) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * warning and error serialization ([65748ec](https://github.com/webpack-contrib/postcss-loader/commit/65748ece396d0b38713783495d8a64f128d3992b)) 61 | 62 | ## [7.3.0](https://github.com/webpack-contrib/postcss-loader/compare/v7.2.4...v7.3.0) (2023-04-28) 63 | 64 | 65 | ### Features 66 | 67 | * use `jiti` for typescript configurations ([#649](https://github.com/webpack-contrib/postcss-loader/issues/649)) ([8b876fa](https://github.com/webpack-contrib/postcss-loader/commit/8b876fa49c71c434b9c5598b179a4f88cf8123e4)) 68 | 69 | ### [7.2.4](https://github.com/webpack-contrib/postcss-loader/compare/v7.2.3...v7.2.4) (2023-04-04) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * memory leak ([#642](https://github.com/webpack-contrib/postcss-loader/issues/642)) ([7ab3b59](https://github.com/webpack-contrib/postcss-loader/commit/7ab3b591dd108732aeab1178d452763936105eae)) 75 | 76 | ### [7.2.3](https://github.com/webpack-contrib/postcss-loader/compare/v7.2.2...v7.2.3) (2023-04-03) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * `ts-node` loading ([#640](https://github.com/webpack-contrib/postcss-loader/issues/640)) ([38b1992](https://github.com/webpack-contrib/postcss-loader/commit/38b199285e02ec767ebebd366180b663731c38cb)) 82 | 83 | ### [7.2.2](https://github.com/webpack-contrib/postcss-loader/compare/v7.2.1...v7.2.2) (2023-04-03) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * cannot find module 'ts-node' ([#639](https://github.com/webpack-contrib/postcss-loader/issues/639)) ([ab4d16a](https://github.com/webpack-contrib/postcss-loader/commit/ab4d16a55b3539cc2b160727b426c6deef75ace9)) 89 | 90 | ### [7.2.1](https://github.com/webpack-contrib/postcss-loader/compare/v7.2.0...v7.2.1) (2023-04-03) 91 | 92 | 93 | ### Bug Fixes 94 | 95 | * cosmiconfig typescript loader ([#635](https://github.com/webpack-contrib/postcss-loader/issues/635)) ([129f5be](https://github.com/webpack-contrib/postcss-loader/commit/129f5be42ead5c5a2b0f654631bcb94fa3d76a95)) 96 | 97 | ## [7.2.0](https://github.com/webpack-contrib/postcss-loader/compare/v7.1.0...v7.2.0) (2023-04-03) 98 | 99 | 100 | ### Features 101 | 102 | * add support for TypeScript based configs ([#632](https://github.com/webpack-contrib/postcss-loader/issues/632)) ([c6b5def](https://github.com/webpack-contrib/postcss-loader/commit/c6b5def4459df0492d4d9175748823e89fa8f3ed)) 103 | 104 | ## [7.1.0](https://github.com/webpack-contrib/postcss-loader/compare/v7.0.2...v7.1.0) (2023-03-16) 105 | 106 | 107 | ### Features 108 | 109 | * **deps:** update `cosmiconfig` ([#628](https://github.com/webpack-contrib/postcss-loader/issues/628)) ([8114be4](https://github.com/webpack-contrib/postcss-loader/commit/8114be41a46843cbf614bc422e3cafcb8e853860)) 110 | 111 | ### [7.0.2](https://github.com/webpack-contrib/postcss-loader/compare/v7.0.1...v7.0.2) (2022-11-29) 112 | 113 | 114 | ### Bug Fixes 115 | 116 | * support ESM version of `postcss.config.js` and `postcss.config.mjs` ([#614](https://github.com/webpack-contrib/postcss-loader/issues/614)) ([955085f](https://github.com/webpack-contrib/postcss-loader/commit/955085f04f5d12bb89d660d88159169b80d5eb99)) 117 | 118 | ### [7.0.1](https://github.com/webpack-contrib/postcss-loader/compare/v7.0.0...v7.0.1) (2022-07-11) 119 | 120 | 121 | ### Bug Fixes 122 | 123 | * unexpected failing on CSS syntax error ([#593](https://github.com/webpack-contrib/postcss-loader/issues/593)) ([888d72e](https://github.com/webpack-contrib/postcss-loader/commit/888d72e340b6d176e95c15d5f96ca412af86a66a)) 124 | 125 | ## [7.0.0](https://github.com/webpack-contrib/postcss-loader/compare/v6.2.1...v7.0.0) (2022-05-18) 126 | 127 | 128 | ### ⚠ BREAKING CHANGES 129 | 130 | * minimum supported `Node.js` version is `14.15.0` 131 | 132 | ### [6.2.1](https://github.com/webpack-contrib/postcss-loader/compare/v6.2.0...v6.2.1) (2021-11-26) 133 | 134 | 135 | ### Bug Fixes 136 | 137 | * watching configuration ([#553](https://github.com/webpack-contrib/postcss-loader/issues/553)) ([7f165b4](https://github.com/webpack-contrib/postcss-loader/commit/7f165b4991b59085651b517d960556a77b72a4d6)) 138 | 139 | ## [6.2.0](https://github.com/webpack-contrib/postcss-loader/compare/v6.1.1...v6.2.0) (2021-10-13) 140 | 141 | 142 | ### Features 143 | 144 | * add `link` field in schema ([#540](https://github.com/webpack-contrib/postcss-loader/issues/540)) ([1ae8212](https://github.com/webpack-contrib/postcss-loader/commit/1ae82129bce9039ac25f7254aa9ba8827ab79b3e)) 145 | 146 | ### [6.1.1](https://github.com/webpack-contrib/postcss-loader/compare/v6.1.0...v6.1.1) (2021-07-01) 147 | 148 | 149 | ### Bug Fixes 150 | 151 | * do not swallow exception from postcss ([2eec42b](https://github.com/webpack-contrib/postcss-loader/commit/2eec42b1a61bbafa039627d3071ba2a1be03de9f)) 152 | 153 | ## [6.1.0](https://github.com/webpack-contrib/postcss-loader/compare/v6.0.0...v6.1.0) (2021-06-10) 154 | 155 | 156 | ### Features 157 | 158 | * allow `String` value for the "implementation" option ([0d342b1](https://github.com/webpack-contrib/postcss-loader/commit/0d342b16dabf58c499da4e13310fdfa5c05badd9)) 159 | 160 | ## [6.0.0](https://github.com/webpack-contrib/postcss-loader/compare/v5.3.0...v6.0.0) (2021-06-10) 161 | 162 | 163 | ### ⚠ BREAKING CHANGES 164 | 165 | * minimum supported `Node.js` version is `12.13.0` (#526) 166 | 167 | ### Bug Fixes 168 | 169 | * check postcss as project dependency ([570db67](https://github.com/webpack-contrib/postcss-loader/commit/570db6726e4f86c966bc35c13637e0aad00f7a1a)) 170 | 171 | 172 | ## [5.3.0](https://github.com/webpack-contrib/postcss-loader/compare/v5.2.0...v5.3.0) (2021-05-14) 173 | 174 | 175 | ### Features 176 | 177 | * add support for `dir-dependency` message type ([#524](https://github.com/webpack-contrib/postcss-loader/issues/524)) ([91dea60](https://github.com/webpack-contrib/postcss-loader/commit/91dea607623d3f9a2c3f7c989243934ba5274296)) 178 | 179 | ## [5.2.0](https://github.com/webpack-contrib/postcss-loader/compare/v5.1.0...v5.2.0) (2021-03-11) 180 | 181 | 182 | ### Features 183 | 184 | * support `ecma` modules for the 'parser', 'stringifier' and 'syntax' options ([#519](https://github.com/webpack-contrib/postcss-loader/issues/519)) ([cc69754](https://github.com/webpack-contrib/postcss-loader/commit/cc69754383f6f9881ac5f02ec489eb89db2cfb0d)) 185 | 186 | ## [5.1.0](https://github.com/webpack-contrib/postcss-loader/compare/v5.0.0...v5.1.0) (2021-03-05) 187 | 188 | 189 | ### Features 190 | 191 | * added support for registering `context`, `build` and `missing` dependencies ([#518](https://github.com/webpack-contrib/postcss-loader/issues/518)) ([9ce4972](https://github.com/webpack-contrib/postcss-loader/commit/9ce4972c6740c8dd82463fff0934987695ccf7d2)), please read [docs](https://github.com/webpack-contrib/postcss-loader#add-dependencies-contextdependencies-builddependencies-missingdependencies) 192 | 193 | ## [5.0.0](https://github.com/webpack-contrib/postcss-loader/compare/v4.2.0...v5.0.0) (2021-02-02) 194 | 195 | 196 | ### ⚠ BREAKING CHANGES 197 | 198 | * minimum supported `webpack` version is `5` 199 | 200 | ## [4.2.0](https://github.com/webpack-contrib/postcss-loader/compare/v4.1.0...v4.2.0) (2021-01-21) 201 | 202 | 203 | ### Features 204 | 205 | * added the `implementation` option ([#511](https://github.com/webpack-contrib/postcss-loader/issues/511)) ([deac978](https://github.com/webpack-contrib/postcss-loader/commit/deac9787eed614b1c445f091a2b70736a6212812)) 206 | 207 | ## [4.1.0](https://github.com/webpack-contrib/postcss-loader/compare/v4.0.4...v4.1.0) (2020-11-19) 208 | 209 | 210 | ### Features 211 | 212 | * partial compatibility with `postcss-cli`, added `api.env` (alias for `api.mode`) and `api.options` (contains options from the `postcssOptions` options), please look at the [example](https://github.com/webpack-contrib/postcss-loader#examples-of-config-files) for more details ([#498](https://github.com/webpack-contrib/postcss-loader/issues/498)) ([84a9c46](https://github.com/webpack-contrib/postcss-loader/commit/84a9c46467086df0185519ceb93bf66893af4cf2)) 213 | 214 | ### [4.0.4](https://github.com/webpack-contrib/postcss-loader/compare/v4.0.3...v4.0.4) (2020-10-09) 215 | 216 | ### Chore 217 | 218 | * update `schema-utils` 219 | 220 | ## [4.0.3](https://github.com/webpack-contrib/postcss-loader/compare/v4.0.2...v4.0.3) (2020-10-02) 221 | 222 | 223 | ### Bug Fixes 224 | 225 | * PostCSS 8 plugin loading ([e1b82fe](https://github.com/webpack-contrib/postcss-loader/commit/e1b82feb9cc27f55953b9237708800cb8c07724e)) 226 | * error and warning messages ([#485](https://github.com/webpack-contrib/postcss-loader/issues/485)) ([4b44e01](https://github.com/webpack-contrib/postcss-loader/commit/4b44e01a323aa9d2c0965e92c1796158cb36ce5a)) 227 | 228 | ### [4.0.2](https://github.com/webpack-contrib/postcss-loader/compare/v4.0.1...v4.0.2) (2020-09-15) 229 | 230 | 231 | ### Bug Fixes 232 | 233 | * compatibility with `postcss@8` ([#479](https://github.com/webpack-contrib/postcss-loader/issues/479)) ([218b0f8](https://github.com/webpack-contrib/postcss-loader/commit/218b0f8013acfafdabea9f561d4c3d074bd2eff3)) 234 | 235 | ### [4.0.1](https://github.com/webpack-contrib/postcss-loader/compare/v4.0.0...v4.0.1) (2020-09-08) 236 | 237 | 238 | ### Bug Fixes 239 | 240 | * source map generation with the `map` option for `postcss` ([#476](https://github.com/webpack-contrib/postcss-loader/issues/476)) ([6babeb1](https://github.com/webpack-contrib/postcss-loader/commit/6babeb1d64ca1e7d3d3651cb07881e1e291fa994)) 241 | 242 | ## [4.0.0](https://github.com/webpack-contrib/postcss-loader/compare/v3.0.0...v4.0.0) (2020-09-07) 243 | 244 | 245 | ### ⚠ BREAKING CHANGES 246 | 247 | * minimum supported `Node.js` version is `10.13` 248 | * minimum supported `webpack` version is `4` 249 | * `postcss` was moved to `peerDependencies`, you need to install `postcss` 250 | * `PostCSS` (`plugins`/`syntax`/`parser`/`stringifier`) options was moved to the `postcssOptions` option, please look at [docs](https://github.com/webpack-contrib/postcss-loader#postcssoptions) 251 | * `sourceMap` default value depends on the `compiler.devtool` option 252 | * the `inline` value was removed for the `sourceMap` option, please use `{ map: { inline: true, annotation: false } }` to achieve this 253 | * source maps contain absolute paths in `sources` 254 | * loader output only CSS, so you need to use `css-loader`/`file-loader`/`raw-loader` to inject code inside bundle 255 | * `exec` option was renamed to the `execute` option 256 | * the `config` option doesn't support `Object` type anymore, `config.path` and `config.ctx` options were removed 257 | * argument in the config for `Function` notation (previously `config.ctx`) was changed, now it contains `{ file, mode, webpackLoaderContext }` 258 | * loader context in the config was renamed from `webpack` to `webpackLoaderContext` 259 | 260 | ### Features 261 | 262 | * message API for emit assets ([#443](https://github.com/webpack-contrib/postcss-loader/issues/443)) ([e966ab9](https://github.com/webpack-contrib/postcss-loader/commit/e966ab965132ca812cb50e5eaf7df5fc2ad9c137)) 263 | * reuse AST from other loaders ([#468](https://github.com/webpack-contrib/postcss-loader/issues/468)) ([9b75888](https://github.com/webpack-contrib/postcss-loader/commit/9b75888dff4957f2ef1e94eca871e329354a9f6d)) 264 | * allows to use config and loader options together, options from the loader takes precedence over the config, the `plugins` option from the config and options are merged ([0eb5aaf](https://github.com/webpack-contrib/postcss-loader/commit/0eb5aaf3d49f6d5e570a3c3fdb6b201487e503c7)) 265 | * `postcssOptions` options can be `Function` 266 | 267 | ### Bug Fixes 268 | 269 | * compatibility with webpack@5 ([#437](https://github.com/webpack-contrib/postcss-loader/issues/437)) ([ed50491](https://github.com/webpack-contrib/postcss-loader/commit/ed504910b70b4d8b4d77084c19ad92330676433e)) 270 | * `default` export for plugins ([#465](https://github.com/webpack-contrib/postcss-loader/issues/465)) ([3d32c35](https://github.com/webpack-contrib/postcss-loader/commit/3d32c35c5c911d6bd25dc0c4b5b3cd11408632d7)) 271 | * avoid mutations of loader options and config ([#470](https://github.com/webpack-contrib/postcss-loader/issues/470)) ([cad6f07](https://github.com/webpack-contrib/postcss-loader/commit/cad6f07c7f4923e8ef69ecb402b10bbd08d09530)) 272 | * respect the `map` option from loader options and config ([#458](https://github.com/webpack-contrib/postcss-loader/issues/458)) ([98441ff](https://github.com/webpack-contrib/postcss-loader/commit/98441ff87e51b58e9322d1bebb5eefc5ba417e24)) 273 | 274 | ### Notes 275 | 276 | * you don't need `ident` option for loader 277 | * `Object` syntax for the `plugin` option is soft deprecated, please migrate on `Array` syntax (`plugins: ['postcss-preset-env', ['cssnano', options]]`) 278 | 279 | 280 | # [3.0.0](https://github.com/postcss/postcss-loader/compare/v2.1.6...v3.0.0) (2018-08-08) 281 | 282 | 283 | ### Bug Fixes 284 | 285 | * **index:** add ast version (`meta.ast`) ([f34954f](https://github.com/postcss/postcss-loader/commit/f34954f)) 286 | * **index:** emit `warnings` as an instance of `{Error}` ([8ac6fb5](https://github.com/postcss/postcss-loader/commit/8ac6fb5)) 287 | * **options:** improved `ValidationError` messages ([549ea08](https://github.com/postcss/postcss-loader/commit/549ea08)) 288 | 289 | 290 | ### Chores 291 | 292 | * **package:** update `postcss` v6.0.0...7.0.0 (`dependencies`) ([#375](https://github.com/postcss/postcss-loader/issues/375)) ([daa0da8](https://github.com/postcss/postcss-loader/commit/daa0da8)) 293 | 294 | 295 | ### BREAKING CHANGES 296 | 297 | * **package:** requires `node >= v6.0.0` 298 | 299 | 300 | 301 | 302 | ## [2.1.6](https://github.com/postcss/postcss-loader/compare/v2.1.5...v2.1.6) (2018-07-10) 303 | 304 | 305 | ### Bug Fixes 306 | 307 | * **package:** config memory leak, updates `postcss-load-config` v1.2.0...2.0.0 (`dependencies`) ([0547b12](https://github.com/postcss/postcss-loader/commit/0547b12)) 308 | 309 | 310 | 311 | 312 | ## [2.1.5](https://github.com/postcss/postcss-loader/compare/v2.1.4...v2.1.5) (2018-05-04) 313 | 314 | 315 | ### Bug Fixes 316 | 317 | * **index:** remove `sourceMap` warning ([#361](https://github.com/postcss/postcss-loader/issues/361)) ([4416791](https://github.com/postcss/postcss-loader/commit/4416791)) 318 | 319 | 320 | 321 | 322 | ## [2.1.4](https://github.com/postcss/postcss-loader/compare/v2.1.3...v2.1.4) (2018-04-16) 323 | 324 | 325 | ### Bug Fixes 326 | 327 | * restore loader object in postcss config context ([#355](https://github.com/postcss/postcss-loader/issues/355)) ([2ff1735](https://github.com/postcss/postcss-loader/commit/2ff1735)) 328 | 329 | 330 | 331 | 332 | ## [2.1.3](https://github.com/postcss/postcss-loader/compare/v2.1.2...v2.1.3) (2018-03-20) 333 | 334 | 335 | ### Bug Fixes 336 | 337 | * **options:** revert additionalProperties changes to keep SemVer ([bd7fc38](https://github.com/postcss/postcss-loader/commit/bd7fc38)) 338 | 339 | 340 | 341 | 342 | ## [2.1.2](https://github.com/postcss/postcss-loader/compare/v2.1.1...v2.1.2) (2018-03-17) 343 | 344 | 345 | ### Bug Fixes 346 | 347 | * **options:** disallow additional properties and add `ident` to validation ([#346](https://github.com/postcss/postcss-loader/issues/346)) ([82ef553](https://github.com/postcss/postcss-loader/commit/82ef553)) 348 | 349 | 350 | 351 | 352 | ## [2.1.1](https://github.com/postcss/postcss-loader/compare/v2.1.0...v2.1.1) (2018-02-26) 353 | 354 | 355 | ### Bug Fixes 356 | 357 | * **index:** don't set `to` value (`options`) ([#339](https://github.com/postcss/postcss-loader/issues/339)) ([cdbb8b6](https://github.com/postcss/postcss-loader/commit/cdbb8b6)) 358 | 359 | 360 | 361 | 362 | # [2.1.0](https://github.com/postcss/postcss-loader/compare/v2.0.10...v2.1.0) (2018-02-02) 363 | 364 | 365 | ### Bug Fixes 366 | 367 | * **index:** continue watching after dependency `{Error}` ([#332](https://github.com/postcss/postcss-loader/issues/332)) ([a8921cc](https://github.com/postcss/postcss-loader/commit/a8921cc)) 368 | 369 | 370 | ### Features 371 | 372 | * **index:** pass AST (`result.root`) && Messages (`result.messages`) as metadata to other loaders ([#322](https://github.com/postcss/postcss-loader/issues/322)) ([56232e7](https://github.com/postcss/postcss-loader/commit/56232e7)) 373 | 374 | 375 | 376 | 377 | ## [2.0.10](https://github.com/postcss/postcss-loader/compare/v2.0.9...v2.0.10) (2018-01-03) 378 | 379 | 380 | ### Bug Fixes 381 | 382 | * **index:** copy loader `options` before modifying ([#326](https://github.com/postcss/postcss-loader/issues/326)) ([61ff03c](https://github.com/postcss/postcss-loader/commit/61ff03c)) 383 | 384 | 385 | 386 | 387 | ## [2.0.9](https://github.com/postcss/postcss-loader/compare/v2.0.8...v2.0.9) (2017-11-24) 388 | 389 | 390 | ### Bug Fixes 391 | 392 | * **index:** filter `ident` (`options.ident`) ([#315](https://github.com/postcss/postcss-loader/issues/315)) ([3e1c7fa](https://github.com/postcss/postcss-loader/commit/3e1c7fa)) 393 | 394 | 395 | 396 | 397 | ## [2.0.8](https://github.com/postcss/postcss-loader/compare/v2.0.6...v2.0.8) (2017-10-14) 398 | 399 | 400 | ### Bug Fixes 401 | 402 | * **lib/options:** handle `{Object}` return (`options.plugins`) ([#301](https://github.com/postcss/postcss-loader/issues/301)) ([df010a7](https://github.com/postcss/postcss-loader/commit/df010a7)) 403 | * **schema:** allow to pass an `{Object}` (`options.syntax/options.stringifier`) ([#300](https://github.com/postcss/postcss-loader/issues/300)) ([58e9996](https://github.com/postcss/postcss-loader/commit/58e9996)) 404 | 405 | 406 | 407 | 408 | ## [2.0.7](https://github.com/postcss/postcss-loader/compare/v2.0.6...v2.0.7) (2017-10-10) 409 | 410 | 411 | ### Bug Fixes 412 | 413 | * sanitizing `from` and `to` options (`postcss.config.js`) ([#260](https://github.com/postcss/postcss-loader/issues/260)) ([753dea7](https://github.com/postcss/postcss-loader/commit/753dea7)) 414 | * **index:** runaway promise ([#269](https://github.com/postcss/postcss-loader/issues/269)) ([8df20ce](https://github.com/postcss/postcss-loader/commit/8df20ce)) 415 | 416 | 417 | 418 | 419 | ## [2.0.6](https://github.com/postcss/postcss-loader/compare/v2.0.5...v2.0.6) (2017-06-14) 420 | 421 | 422 | ### Bug Fixes 423 | 424 | * allow to pass an `{Object}` (`options.parser`) ([#257](https://github.com/postcss/postcss-loader/issues/257)) ([4974607](https://github.com/postcss/postcss-loader/commit/4974607)) 425 | * misspelling in warnings ([#237](https://github.com/postcss/postcss-loader/issues/237)) ([adcbb2e](https://github.com/postcss/postcss-loader/commit/adcbb2e)) 426 | * **index:** simplify config loading behaviour ([#259](https://github.com/postcss/postcss-loader/issues/259)) ([b313478](https://github.com/postcss/postcss-loader/commit/b313478)) 427 | 428 | 429 | 430 | 431 | ## [2.0.5](https://github.com/postcss/postcss-loader/compare/v2.0.4...v2.0.5) (2017-05-10) 432 | 433 | 434 | ### Bug Fixes 435 | 436 | * regression with `options.plugins` `{Function}` (`webpack.config.js`) (#229) ([dca52a9](https://github.com/postcss/postcss-loader/commit/dca52a9)) 437 | 438 | 439 | 440 | 441 | ## [2.0.4](https://github.com/postcss/postcss-loader/compare/v2.0.3...v2.0.4) (2017-05-10) 442 | 443 | 444 | ### Bug Fixes 445 | 446 | * **index:** `postcss.config.js` not resolved correctly (`options.config`) ([faaeee4](https://github.com/postcss/postcss-loader/commit/faaeee4)) 447 | * **index:** update validation schema, better warning message ([4f20c99](https://github.com/postcss/postcss-loader/commit/4f20c99)) 448 | 449 | 450 | 451 | 452 | ## [2.0.3](https://github.com/postcss/postcss-loader/compare/v2.0.2...v2.0.3) (2017-05-09) 453 | 454 | 455 | ### Bug Fixes 456 | 457 | * **index:** don't fail on 'sourceMap: false' && emit a warning instead, when previous map found (`options.sourceMap`) ([159b66a](https://github.com/postcss/postcss-loader/commit/159b66a)) 458 | 459 | 460 | 461 | 462 | ## [2.0.2](https://github.com/postcss/postcss-loader/compare/v2.0.1...v2.0.2) (2017-05-09) 463 | 464 | 465 | ### Bug Fixes 466 | 467 | * **index:** 'No PostCSS Config found' (`options.config`) (#215) ([e764761](https://github.com/postcss/postcss-loader/commit/e764761)) 468 | 469 | 470 | 471 | 472 | ## [2.0.1](https://github.com/postcss/postcss-loader/compare/v2.0.0...v2.0.1) (2017-05-08) 473 | 474 | 475 | ### Bug Fixes 476 | 477 | * **index:** 'Cannot create property `prev` on boolean `false`' (`options.sourceMap`) ([c4f0064](https://github.com/postcss/postcss-loader/commit/c4f0064)) 478 | 479 | 480 | 481 | 482 | # [2.0.0](https://github.com/postcss/postcss-loader/compare/1.2.2...v2.0.0) (2017-05-08) 483 | 484 | 485 | ### Features 486 | 487 | * **index:** add ctx, ctx.file, ctx.options ([0dceb2c](https://github.com/postcss/postcss-loader/commit/0dceb2c)) 488 | * **index:** add options validation ([2b76df8](https://github.com/postcss/postcss-loader/commit/2b76df8)) 489 | 490 | 491 | 492 | ## 1.3.3 493 | * Remove `postcss-loader-before-processing` warning (by Michael Ciniawsky). 494 | 495 | ## 1.3.2 496 | * Fix deprecated warning (by Xiaoyu Zhai). 497 | 498 | ## 1.3.1 499 | * Fix conflict with CLI `--config` argument (by EGOIST). 500 | 501 | ## 1.3 502 | * Allow object in syntax options, not only path for require (by Jeff Escalante). 503 | 504 | ## 1.2.2 505 | * Watch `postcss.config.js` for changes (by Michael Ciniawsky). 506 | 507 | ## 1.2.1 508 | * Fix relative `config` parameter resolving (by Simen Bekkhus). 509 | 510 | ## 1.2 511 | * Add `config` parameter (by sainthkh). 512 | 513 | ## 1.1.1 514 | * Fix `this` in options function (by Jeff Escalante). 515 | 516 | ## 1.1 517 | * PostCSS common config could be placed to subdirs. 518 | * Add webpack instance to PostCSS common config context. 519 | 520 | ## 1.0 521 | * Add common PostCSS config support (by Mateusz Derks). 522 | * Add Webpack 2 support with `plugins` query option (by Izaak Schroeder). 523 | * Add `dependency` message support. 524 | * Rewrite docs (by Michael Ciniawsky). 525 | 526 | ## 0.13 527 | * Add `exec` parameter (by Neal Granger). 528 | 529 | ## 0.12 530 | * Add CSS syntax highlight to syntax error code frame. 531 | 532 | ## 0.11.1 533 | * Fix Promise API (by Daniel Haus). 534 | 535 | ## 0.11 536 | * Add `postcss-loader-before-processing` webpack event (by Jan Nicklas). 537 | 538 | ## 0.10.1 539 | * Better syntax error message (by Andrey Popp). 540 | 541 | ## 0.10.0 542 | * Add `sourceMap` parameter to force inline maps (by 雪狼). 543 | 544 | ## 0.9.1 545 | * Fix plugin in simple array config. 546 | 547 | ## 0.9 548 | * Allow to pass syntax, parser or stringifier as function (by Jeff Escalante). 549 | 550 | ## 0.8.2 551 | * Fix source map support (by Andrew Bradley). 552 | 553 | ## 0.8.1 554 | * Fix resource path. 555 | 556 | ## 0.8 557 | * Add postcss-js support (by Simon Degraeve). 558 | 559 | ## 0.7 560 | * Added argument with webpack instance to plugins callback (by Maxime Thirouin). 561 | 562 | ## 0.6 563 | * Use PostCSS 5.0. 564 | * Remove `safe` parameter. Use Safe Parser. 565 | * Add `syntax`, `parser` and `stringifier` parameters. 566 | 567 | ## 0.5.1 568 | * Fix string source map support (by Jan Nicklas). 569 | 570 | ## 0.5.0 571 | * Set plugins by function for hot reload support (by Stefano Brilli). 572 | 573 | ## 0.4.4 574 | * Fix error on empty PostCSS config. 575 | 576 | ## 0.4.3 577 | * Better check for `CssSyntaxError`. 578 | 579 | ## 0.4.2 580 | * Fixed invalid sourcemap exception (by Richard Willis). 581 | 582 | ## 0.4.1 583 | * Use only Promise API to catch PostCSS errors. 584 | 585 | ## 0.4 586 | * Add PostCSS asynchronous API support. 587 | * Fix source map support (by Richard Willis). 588 | * Add warnings API support. 589 | * Better output for CSS syntax errors. 590 | 591 | ## 0.3 592 | * Use PostCSS 4.0. 593 | 594 | ## 0.2 595 | * Use PostCSS 3.0. 596 | 597 | ## 0.1 598 | * Initial release. 599 | --------------------------------------------------------------------------------