├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .rollup.js ├── .tape.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── INSTALL.md ├── LICENSE.md ├── README.md ├── package.json ├── src ├── index.js └── lib │ ├── get-fn-value.js │ ├── get-replaced-value.js │ ├── import-from.js │ ├── is-env-func.js │ ├── update-env-value.js │ └── walk-env-funcs.js └── test ├── basic.css ├── basic.expect.css ├── basic.import-is-empty.expect.css ├── basic.import.expect.css ├── import-variables.cjs ├── import-variables.js └── import-variables.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | 13 | [*.{json,md,yml}] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node: [12, 14, 16] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: ${{ matrix.node }} 16 | 17 | - run: npm install --ignore-scripts 18 | - run: npm run test 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | package-lock.json 4 | yarn.lock 5 | *.log* 6 | *.result.css 7 | .* 8 | !.editorconfig 9 | !.gitattributes 10 | !.gitignore 11 | !.rollup.js 12 | !.tape.js 13 | !.github 14 | -------------------------------------------------------------------------------- /.rollup.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' 2 | 3 | export default { 4 | ...pkg.rollup, 5 | plugins: pkg.rollup.plugins.map(plugin => require(plugin).default()), 6 | onwarn(warning, warn) { 7 | if (warning.code !== 'UNRESOLVED_IMPORT') warn(warning) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.tape.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'basic': { 3 | message: 'supports basic usage', 4 | warnings: 1 5 | }, 6 | 'basic:import': { 7 | message: 'supports { importFrom: { environmentVariables: { ... } } } usage', 8 | warnings: 1, 9 | options: { 10 | importFrom: { 11 | environmentVariables: { 12 | '--some-custom-padding': '20px', 13 | '--another-custom-width': '600px' 14 | } 15 | } 16 | } 17 | }, 18 | 'basic:import-fn': { 19 | message: 'supports { importFrom() } usage', 20 | warnings: 1, 21 | options: { 22 | importFrom() { 23 | return { 24 | environmentVariables: { 25 | '--some-custom-padding': '20px', 26 | '--another-custom-width': '600px' 27 | } 28 | }; 29 | } 30 | }, 31 | expect: 'basic.import.expect.css', 32 | result: 'basic.import.result.css' 33 | }, 34 | 'basic:import-fn-promise': { 35 | message: 'supports { async importFrom() } usage', 36 | warnings: 1, 37 | options: { 38 | importFrom() { 39 | return new Promise(resolve => { 40 | resolve({ 41 | environmentVariables: { 42 | '--some-custom-padding': '20px', 43 | '--another-custom-width': '600px' 44 | } 45 | }) 46 | }); 47 | } 48 | }, 49 | expect: 'basic.import.expect.css', 50 | result: 'basic.import.result.css' 51 | }, 52 | 'basic:import-json': { 53 | message: 'supports { importFrom: "test/import-variables.json" } usage', 54 | warnings: 1, 55 | options: { 56 | importFrom: 'test/import-variables.json' 57 | }, 58 | expect: 'basic.import.expect.css', 59 | result: 'basic.import.result.css' 60 | }, 61 | 'basic:import-js': { 62 | message: 'supports { importFrom: "test/import-variables.js" } usage', 63 | warnings: 1, 64 | options: { 65 | importFrom: 'test/import-variables.js' 66 | }, 67 | expect: 'basic.import.expect.css', 68 | result: 'basic.import.result.css' 69 | }, 70 | 'basic:import-cjs': { 71 | message: 'supports { importFrom: "test/import-variables.cjs" } usage', 72 | warnings: 1, 73 | options: { 74 | importFrom: 'test/import-variables.cjs' 75 | }, 76 | expect: 'basic.import.expect.css', 77 | result: 'basic.import.result.css' 78 | }, 79 | 'basic:import-js-from': { 80 | message: 'supports { importFrom: { from: "test/import-variables.js" } } usage', 81 | warnings: 1, 82 | options: { 83 | importFrom: { from: 'test/import-variables.js' } 84 | }, 85 | expect: 'basic.import.expect.css', 86 | result: 'basic.import.result.css' 87 | }, 88 | 'basic:import-js-from-type': { 89 | message: 'supports { importFrom: [ { from: "test/import-variables.js", type: "js" } ] } usage', 90 | warnings: 1, 91 | options: { 92 | importFrom: [ { from: 'test/import-variables.js', type: 'js' } ] 93 | }, 94 | expect: 'basic.import.expect.css', 95 | result: 'basic.import.result.css' 96 | }, 97 | 'basic:import-is-empty': { 98 | message: 'supports { importFrom: {} } usage', 99 | warnings: 1, 100 | options: { 101 | importFrom: {} 102 | } 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes to PostCSS Environment Variables 2 | 3 | ### 4.0.2 (November 18, 2021) 4 | 5 | - Added: Safeguards against postcss-values-parser potentially throwing an error. 6 | 7 | ### 4.0.1 (October 28, 2021) 8 | 9 | - Updated: Enforcing styling consistency 10 | - Updated: `postcss-values-parser` to 6.0.1 (patch). 11 | 12 | ### 4.0.0 (September 17, 2021) 13 | 14 | - Updated: Support for PostCS 8+ (major). 15 | - Updated: Support for Node 12+ (major). 16 | 17 | ### 3.0.0 (June 13, 2019) 18 | 19 | - Updated: `postcss-values-parser` to 3.2.0 (major) 20 | - Updated: `postcss` to 7.0.27 (patch) 21 | - Updated: Support for Node 10+ (major) 22 | 23 | ### 2.0.2 (September 20, 2018) 24 | 25 | - Updated: Do not break on an empty importFrom object 26 | 27 | ### 2.0.1 (September 18, 2018) 28 | 29 | - Updated: Support for PostCSS Values Parser 2 30 | 31 | ### 2.0.0 (September 17, 2018) 32 | 33 | - Updated: Support for PostCSS 7+ 34 | - Updated: Support for Node 6+ 35 | - Updated: Changed `variables` option to `importFrom` option 36 | 37 | ### 1.0.0 (April 28, 2018) 38 | 39 | - Initial version 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to PostCSS Environment Variables 2 | 3 | You want to help? You rock! Now, take a moment to be sure your contributions 4 | make sense to everyone else. 5 | 6 | ## Reporting Issues 7 | 8 | Found a problem? Want a new feature? 9 | 10 | - See if your issue or idea has [already been reported]. 11 | - Provide a [reduced test case] or a [live example]. 12 | 13 | Remember, a bug is a _demonstrable problem_ caused by _our_ code. 14 | 15 | ## Submitting Pull Requests 16 | 17 | Pull requests are the greatest contributions, so be sure they are focused in 18 | scope and avoid unrelated commits. 19 | 20 | 1. To begin; [fork this project], clone your fork, and add our upstream. 21 | ```bash 22 | # Clone your fork of the repo into the current directory 23 | git clone git@github.com:YOUR_USER/postcss-env-function.git 24 | 25 | # Navigate to the newly cloned directory 26 | cd postcss-env-function 27 | 28 | # Assign the original repo to a remote called "upstream" 29 | git remote add upstream git@github.com:csstools/postcss-env-function.git 30 | 31 | # Install the tools necessary for testing 32 | npm install 33 | ``` 34 | 35 | 2. Create a branch for your feature or fix: 36 | ```bash 37 | # Move into a new branch for your feature 38 | git checkout -b feature/thing 39 | ``` 40 | ```bash 41 | # Move into a new branch for your fix 42 | git checkout -b fix/something 43 | ``` 44 | 45 | 3. If your code follows our practices, then push your feature branch: 46 | ```bash 47 | # Test current code 48 | npm test 49 | ``` 50 | ```bash 51 | # Push the branch for your new feature 52 | git push origin feature/thing 53 | ``` 54 | ```bash 55 | # Or, push the branch for your update 56 | git push origin update/something 57 | ``` 58 | 59 | That’s it! Now [open a pull request] with a clear title and description. 60 | 61 | [already been reported]: issues 62 | [fork this project]: fork 63 | [live example]: https://codepen.io/pen 64 | [open a pull request]: https://help.github.com/articles/using-pull-requests/ 65 | [reduced test case]: https://css-tricks.com/reduced-test-cases/ 66 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installing PostCSS Environment Variables 2 | 3 | [PostCSS Environment Variables] runs in all Node environments, with special instructions for: 4 | 5 | | [Node](#node) | [PostCSS CLI](#postcss-cli) | [Webpack](#webpack) | [Create React App](#create-react-app) | [Gulp](#gulp) | [Grunt](#grunt) | 6 | | --- | --- | --- | --- | --- | --- | 7 | 8 | ## Node 9 | 10 | Add [PostCSS Environment Variables] to your project: 11 | 12 | ```bash 13 | npm install postcss-env-function --save-dev 14 | ``` 15 | 16 | Use [PostCSS Environment Variables] to process your CSS: 17 | 18 | ```js 19 | const postcssEnvFunction = require('postcss-env-function'); 20 | 21 | postcssEnvFunction.process(YOUR_CSS /*, processOptions, pluginOptions */); 22 | ``` 23 | 24 | Or use it as a [PostCSS] plugin: 25 | 26 | ```js 27 | const postcss = require('postcss'); 28 | const postcssEnvFunction = require('postcss-env-function'); 29 | 30 | postcss([ 31 | postcssEnvFunction(/* pluginOptions */) 32 | ]).process(YOUR_CSS /*, processOptions */); 33 | ``` 34 | 35 | ## PostCSS CLI 36 | 37 | Add [PostCSS CLI] to your project: 38 | 39 | ```bash 40 | npm install postcss-cli --save-dev 41 | ``` 42 | 43 | Use [PostCSS Environment Variables] in your `postcss.config.js` configuration file: 44 | 45 | ```js 46 | const postcssEnvFunction = require('postcss-env-function'); 47 | 48 | module.exports = { 49 | plugins: [ 50 | postcssEnvFunction(/* pluginOptions */) 51 | ] 52 | } 53 | ``` 54 | 55 | ## Webpack 56 | 57 | Add [PostCSS Loader] to your project: 58 | 59 | ```bash 60 | npm install postcss-loader --save-dev 61 | ``` 62 | 63 | Use [PostCSS Environment Variables] in your Webpack configuration: 64 | 65 | ```js 66 | const postcssEnvFunction = require('postcss-env-function'); 67 | 68 | module.exports = { 69 | module: { 70 | rules: [ 71 | { 72 | test: /\.css$/, 73 | use: [ 74 | 'style-loader', 75 | { loader: 'css-loader', options: { importLoaders: 1 } }, 76 | { loader: 'postcss-loader', options: { 77 | ident: 'postcss', 78 | plugins: () => [ 79 | postcssEnvFunction(/* pluginOptions */) 80 | ] 81 | } } 82 | ] 83 | } 84 | ] 85 | } 86 | } 87 | ``` 88 | 89 | ## Create React App 90 | 91 | Add [React App Rewired] and [React App Rewire PostCSS] to your project: 92 | 93 | ```bash 94 | npm install react-app-rewired react-app-rewire-postcss --save-dev 95 | ``` 96 | 97 | Use [React App Rewire PostCSS] and [PostCSS Environment Variables] in your 98 | `config-overrides.js` file: 99 | 100 | ```js 101 | const reactAppRewirePostcss = require('react-app-rewire-postcss'); 102 | const postcssEnvFunction = require('postcss-env-function'); 103 | 104 | module.exports = config => reactAppRewirePostcss(config, { 105 | plugins: () => [ 106 | postcssEnvFunction(/* pluginOptions */) 107 | ] 108 | }); 109 | ``` 110 | 111 | ## Gulp 112 | 113 | Add [Gulp PostCSS] to your project: 114 | 115 | ```bash 116 | npm install gulp-postcss --save-dev 117 | ``` 118 | 119 | Use [PostCSS Environment Variables] in your Gulpfile: 120 | 121 | ```js 122 | const postcss = require('gulp-postcss'); 123 | const postcssEnvFunction = require('postcss-env-function'); 124 | 125 | gulp.task('css', () => gulp.src('./src/*.css').pipe( 126 | postcss([ 127 | postcssEnvFunction(/* pluginOptions */) 128 | ]) 129 | ).pipe( 130 | gulp.dest('.') 131 | )); 132 | ``` 133 | 134 | ## Grunt 135 | 136 | Add [Grunt PostCSS] to your project: 137 | 138 | ```bash 139 | npm install grunt-postcss --save-dev 140 | ``` 141 | 142 | Use [PostCSS Environment Variables] in your Gruntfile: 143 | 144 | ```js 145 | const postcssEnvFunction = require('postcss-env-function'); 146 | 147 | grunt.loadNpmTasks('grunt-postcss'); 148 | 149 | grunt.initConfig({ 150 | postcss: { 151 | options: { 152 | use: [ 153 | postcssEnvFunction(/* pluginOptions */) 154 | ] 155 | }, 156 | dist: { 157 | src: '*.css' 158 | } 159 | } 160 | }); 161 | ``` 162 | 163 | [Gulp PostCSS]: https://github.com/postcss/gulp-postcss 164 | [Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss 165 | [PostCSS]: https://github.com/postcss/postcss 166 | [PostCSS CLI]: https://github.com/postcss/postcss-cli 167 | [PostCSS Loader]: https://github.com/postcss/postcss-loader 168 | [PostCSS Environment Variables]: https://github.com/csstools/postcss-env-function 169 | [React App Rewire PostCSS]: https://github.com/csstools/react-app-rewire-postcss 170 | [React App Rewired]: https://github.com/timarney/react-app-rewired 171 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # CC0 1.0 Universal 2 | 3 | ## Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an “owner”) of an original work of 8 | authorship and/or a database (each, a “Work”). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific works 12 | (“Commons”) that the public can reliably and without fear of later claims of 13 | infringement build upon, modify, incorporate in other works, reuse and 14 | redistribute as freely as possible in any form whatsoever and for any purposes, 15 | including without limitation commercial purposes. These owners may contribute 16 | to the Commons to promote the ideal of a free culture and the further 17 | production of creative, cultural and scientific works, or to gain reputation or 18 | greater distribution for their Work in part through the use and efforts of 19 | others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation of 22 | additional consideration or compensation, the person associating CC0 with a 23 | Work (the “Affirmer”), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and 25 | publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights (“Copyright and 31 | Related Rights”). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 1. the right to reproduce, adapt, distribute, perform, display, communicate, 34 | and translate a Work; 35 | 2. moral rights retained by the original author(s) and/or performer(s); 36 | 3. publicity and privacy rights pertaining to a person’s image or likeness 37 | depicted in a Work; 38 | 4. rights protecting against unfair competition in regards to a Work, 39 | subject to the limitations in paragraph 4(i), below; 40 | 5. rights protecting the extraction, dissemination, use and reuse of data in 41 | a Work; 42 | 6. database rights (such as those arising under Directive 96/9/EC of the 43 | European Parliament and of the Council of 11 March 1996 on the legal 44 | protection of databases, and under any national implementation thereof, 45 | including any amended or successor version of such directive); and 46 | 7. other similar, equivalent or corresponding rights throughout the world 47 | based on applicable law or treaty, and any national implementations 48 | thereof. 49 | 50 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 51 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 52 | unconditionally waives, abandons, and surrenders all of Affirmer’s Copyright 53 | and Related Rights and associated claims and causes of action, whether now 54 | known or unknown (including existing as well as future claims and causes of 55 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 56 | duration provided by applicable law or treaty (including future time 57 | extensions), (iii) in any current or future medium and for any number of 58 | copies, and (iv) for any purpose whatsoever, including without limitation 59 | commercial, advertising or promotional purposes (the “Waiver”). Affirmer 60 | makes the Waiver for the benefit of each member of the public at large and 61 | to the detriment of Affirmer’s heirs and successors, fully intending that 62 | such Waiver shall not be subject to revocation, rescission, cancellation, 63 | termination, or any other legal or equitable action to disrupt the quiet 64 | enjoyment of the Work by the public as contemplated by Affirmer’s express 65 | Statement of Purpose. 66 | 67 | 3. Public License Fallback. Should any part of the Waiver for any reason be 68 | judged legally invalid or ineffective under applicable law, then the Waiver 69 | shall be preserved to the maximum extent permitted taking into account 70 | Affirmer’s express Statement of Purpose. In addition, to the extent the 71 | Waiver is so judged Affirmer hereby grants to each affected person a 72 | royalty-free, non transferable, non sublicensable, non exclusive, 73 | irrevocable and unconditional license to exercise Affirmer’s Copyright and 74 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 75 | maximum duration provided by applicable law or treaty (including future time 76 | extensions), (iii) in any current or future medium and for any number of 77 | copies, and (iv) for any purpose whatsoever, including without limitation 78 | commercial, advertising or promotional purposes (the “License”). The License 79 | shall be deemed effective as of the date CC0 was applied by Affirmer to the 80 | Work. Should any part of the License for any reason be judged legally 81 | invalid or ineffective under applicable law, such partial invalidity or 82 | ineffectiveness shall not invalidate the remainder of the License, and in 83 | such case Affirmer hereby affirms that he or she will not (i) exercise any 84 | of his or her remaining Copyright and Related Rights in the Work or (ii) 85 | assert any associated claims and causes of action with respect to the Work, 86 | in either case contrary to Affirmer’s express Statement of Purpose. 87 | 88 | 4. Limitations and Disclaimers. 89 | 1. No trademark or patent rights held by Affirmer are waived, abandoned, 90 | surrendered, licensed or otherwise affected by this document. 91 | 2. Affirmer offers the Work as-is and makes no representations or warranties 92 | of any kind concerning the Work, express, implied, statutory or 93 | otherwise, including without limitation warranties of title, 94 | merchantability, fitness for a particular purpose, non infringement, or 95 | the absence of latent or other defects, accuracy, or the present or 96 | absence of errors, whether or not discoverable, all to the greatest 97 | extent permissible under applicable law. 98 | 3. Affirmer disclaims responsibility for clearing rights of other persons 99 | that may apply to the Work or any use thereof, including without 100 | limitation any person’s Copyright and Related Rights in the Work. 101 | Further, Affirmer disclaims responsibility for obtaining any necessary 102 | consents, permissions or other rights required for any use of the Work. 103 | 4. Affirmer understands and acknowledges that Creative Commons is not a 104 | party to this document and has no duty or obligation with respect to this 105 | CC0 or use of the Work. 106 | 107 | For more information, please see 108 | http://creativecommons.org/publicdomain/zero/1.0/. 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
⚠️ PostCSS Environment Variables was moved to @csstools/postcss-plugins. ⚠️
2 | Read the announcement
3 | 4 | # PostCSS Environment Variables [PostCSS Logo][postcss] 5 | 6 | [NPM Version][npm-url] 7 | [CSS Standard Status][css-url] 8 | [Build Status][cli-url] 9 | [Support Chat][git-url] 10 | 11 | [PostCSS Environment Variables] lets you use `env()` variables in CSS, following the [CSS Environment Variables] specification. 12 | 13 | ```pcss 14 | @media (max-width: env(--branding-small)) { 15 | body { 16 | padding: env(--branding-padding); 17 | } 18 | } 19 | 20 | /* becomes */ 21 | 22 | @media (min-width: 600px) { 23 | body { 24 | padding: 20px; 25 | } 26 | } 27 | 28 | /* when the `importFrom` option is: { 29 | "environmentVariables": { 30 | "--branding-small": "600px", 31 | "--branding-padding": "20px" 32 | } 33 | } */ 34 | ``` 35 | 36 | ## Usage 37 | 38 | Add [PostCSS Environment Variables] to your project: 39 | 40 | ```bash 41 | npm install postcss postcss-env-function --save-dev 42 | ``` 43 | 44 | Use [PostCSS Environment Variables] to process your CSS: 45 | 46 | ```js 47 | const postcssEnvFunction = require('postcss-env-function') 48 | 49 | postcssEnvFunction.process(YOUR_CSS /*, processOptions, pluginOptions */) 50 | ``` 51 | 52 | Or use it as a [PostCSS] plugin: 53 | 54 | ```js 55 | const postcss = require('postcss') 56 | const postcssEnvFunction = require('postcss-env-function') 57 | 58 | postcss([ 59 | postcssEnvFunction(/* pluginOptions */) 60 | ]).process(YOUR_CSS /*, processOptions */) 61 | ``` 62 | 63 | [PostCSS Environment Variables] runs in all Node environments, with special instructions for: 64 | 65 | | [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) | 66 | | --- | --- | --- | --- | --- | --- | 67 | 68 | ## Options 69 | 70 | ### importFrom 71 | 72 | The `importFrom` option specifies sources where Environment Variables can be imported from, which might be JS and JSON files, functions, and directly passed objects. 73 | 74 | ```js 75 | postcssEnvFunction({ 76 | importFrom: 'path/to/file.js' /* module.exports = { 77 | environmentVariables: { 78 | '--branding-padding': '20px', 79 | '--branding-small': '600px' 80 | } 81 | } */ 82 | }) 83 | ``` 84 | 85 | ```pcss 86 | @media (max-width: env(--branding-small)) { 87 | body { 88 | padding: env(--branding-padding); 89 | } 90 | } 91 | 92 | /* becomes */ 93 | 94 | @media (min-width: 600px) { 95 | body { 96 | padding: 20px; 97 | } 98 | } 99 | ``` 100 | 101 | Multiple sources can be passed into this option, and they will be parsed in the order they are received. JavaScript files, JSON files, functions, and objects will need to namespace Custom Properties using the `environmentVariables` or `environment-variables` key. 102 | 103 | ```js 104 | postcssEnvFunction({ 105 | importFrom: [ 106 | /* Import from a CommonJS file: 107 | 108 | module.exports = { 109 | environmentVariables: { 110 | '--branding-padding': '20px' 111 | } 112 | } */ 113 | 'path/to/file.js', 114 | 115 | /* Import from a JSON file: 116 | 117 | { 118 | "environment-variables": { 119 | "--branding-padding": "20px" 120 | } 121 | } */ 122 | 'and/then/this.json', 123 | 124 | /* Import from an JavaScript Object: */ 125 | { 126 | environmentVariables: { '--branding-padding': '20px' } 127 | }, 128 | 129 | /* Import from a JavaScript Function: */ 130 | () => { 131 | const environmentVariables = { '--branding-padding': '20px' } 132 | 133 | return { environmentVariables } 134 | } 135 | ] 136 | }) 137 | ``` 138 | 139 | See example imports written in [JS](test/import-variables.js) and [JSON](test/import-variables.json). 140 | Currently only valid [custom property names] (beginning with `--`) are accepted. 141 | Not all valid [declaration value names] are accepted. 142 | 143 | [cli-url]: https://github.com/csstools/postcss-env-function/actions/workflows/test.yml?query=workflow/test 144 | [css-url]: https://cssdb.org/#environment-variables 145 | [git-url]: https://gitter.im/postcss/postcss 146 | [npm-url]: https://www.npmjs.com/package/postcss-env-function 147 | 148 | [CSS Environment Variables]: https://drafts.csswg.org/css-env-1/ 149 | [PostCSS]: https://github.com/postcss/postcss 150 | [PostCSS Environment Variables]: https://github.com/csstools/postcss-env-function 151 | 152 | [custom property names]: https://drafts.csswg.org/css-variables-1/#typedef-custom-property-name 153 | [declaration value names]: https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value 154 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-env-function", 3 | "version": "4.0.2", 4 | "description": "Use env() variables in CSS", 5 | "author": "Jonathan Neal ", 6 | "license": "CC0-1.0", 7 | "repository": "csstools/postcss-env-function", 8 | "homepage": "https://github.com/csstools/postcss-env-function#readme", 9 | "bugs": "https://github.com/csstools/postcss-env-function/issues", 10 | "main": "dist/index.cjs", 11 | "module": "dist/index.mjs", 12 | "files": [ 13 | "dist" 14 | ], 15 | "scripts": { 16 | "build": "npx rollup -c .rollup.js", 17 | "build:watch": "npx rollup -c .rollup.js --watch", 18 | "lint": "npx eslint --cache src", 19 | "lint:fix": "npx eslint --cache --fix", 20 | "pretest": "npm run build", 21 | "test": "npm run lint && npm run tape", 22 | "tape": "postcss-tape", 23 | "prepublishOnly": "npm test" 24 | }, 25 | "engines": { 26 | "node": ">=12" 27 | }, 28 | "dependencies": { 29 | "postcss-values-parser": "6.0.1" 30 | }, 31 | "peerDependencies": { 32 | "postcss": "^8.3" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.15.8", 36 | "@babel/preset-env": "^7.15.8", 37 | "@rollup/plugin-babel": "^5.3.0", 38 | "eslint": "^8.1.0", 39 | "postcss": "^8.3.6", 40 | "postcss-tape": "^6.0.1", 41 | "pre-commit": "^1.2.2", 42 | "rollup": "^2.58.3" 43 | }, 44 | "babel": { 45 | "presets": [ 46 | [ 47 | "@babel/env", 48 | { 49 | "targets": "maintained node versions" 50 | } 51 | ] 52 | ] 53 | }, 54 | "eslintConfig": { 55 | "env": { 56 | "es6": true 57 | }, 58 | "extends": "eslint:recommended", 59 | "parserOptions": { 60 | "ecmaVersion": 12, 61 | "sourceType": "module", 62 | "ecmaFeatures": { 63 | "modules": true 64 | } 65 | }, 66 | "rules": { 67 | "semi": [ 68 | "error", 69 | "never" 70 | ] 71 | } 72 | }, 73 | "rollup": { 74 | "input": "src/index.js", 75 | "plugins": [ 76 | "@rollup/plugin-babel" 77 | ], 78 | "output": [ 79 | { 80 | "exports": "default", 81 | "file": "dist/index.cjs", 82 | "format": "cjs" 83 | }, 84 | { 85 | "file": "dist/index.mjs", 86 | "format": "esm" 87 | } 88 | ] 89 | }, 90 | "keywords": [ 91 | "postcss", 92 | "css", 93 | "postcss-plugin", 94 | "environments", 95 | "variables", 96 | "envs", 97 | "constants", 98 | "functions" 99 | ] 100 | } 101 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import getReplacedValue from './lib/get-replaced-value' 2 | import importEnvironmentVariablesFromSources from './lib/import-from' 3 | 4 | /** 5 | * @param {{importFrom?: string[]}} opts 6 | * @returns {import('postcss').Plugin} 7 | */ 8 | export default function creator(opts) { 9 | // sources to import environment variables from 10 | const importFrom = [].concat(Object(opts).importFrom || []) 11 | 12 | // promise any environment variables are imported 13 | const environmentVariablesPromise = importEnvironmentVariablesFromSources(importFrom) 14 | 15 | return { 16 | postcssPlugin: 'postcss-env-fn', 17 | async AtRule(atRule, { result }) { 18 | let replacedValue 19 | 20 | try { 21 | replacedValue = getReplacedValue(atRule.params, await environmentVariablesPromise) 22 | } catch (error) { 23 | atRule.warn( 24 | result, 25 | `Failed to parse params '${atRule.params}' as an environment value. Leaving the original value intact.` 26 | ) 27 | } 28 | 29 | if (typeof replacedValue === 'undefined') { 30 | return 31 | } 32 | 33 | if (replacedValue !== atRule.params) { 34 | atRule.params = replacedValue 35 | } 36 | }, 37 | async Declaration(decl, { result }) { 38 | let replacedValue 39 | 40 | try { 41 | replacedValue = getReplacedValue(decl.value, await environmentVariablesPromise) 42 | } catch (error) { 43 | decl.warn( 44 | result, 45 | `Failed to parse value '${decl.value}' as an environment value. Leaving the original value intact.` 46 | ) 47 | } 48 | 49 | if (typeof replacedValue === 'undefined') { 50 | return 51 | } 52 | 53 | if (replacedValue !== decl.value) { 54 | decl.value = replacedValue 55 | } 56 | } 57 | } 58 | } 59 | 60 | creator.postcss = true 61 | -------------------------------------------------------------------------------- /src/lib/get-fn-value.js: -------------------------------------------------------------------------------- 1 | const dashedMatch = /^--/ 2 | 3 | // returns the value of a css function as a string 4 | export default (node) => { 5 | const value = String(node.nodes) 6 | 7 | return dashedMatch.test(value) ? value : undefined 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/get-replaced-value.js: -------------------------------------------------------------------------------- 1 | import { parse } from 'postcss-values-parser' 2 | import updateEnvValue from './update-env-value' 3 | import walkEnvFuncs from './walk-env-funcs' 4 | 5 | /** 6 | * @param {string} originalValue 7 | * @param variables 8 | * @returns {string} returns a value replaced with environment variables 9 | */ 10 | export default (originalValue, variables) => { 11 | // get the ast of the original value 12 | const ast = parse(originalValue, { ignoreUnknownWords: true }) 13 | 14 | // walk all of the css env() functions 15 | walkEnvFuncs(ast, node => { 16 | // update the environment value for the css env() function 17 | updateEnvValue(node, variables) 18 | }) 19 | 20 | // return the stringified ast 21 | return String(ast) 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/import-from.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import { parse } from 'postcss-values-parser' 4 | 5 | /** 6 | * Import Custom Properties from Object 7 | * @param {{environmentVariables: Record, 'environment-variables': Record}} object 8 | * @returns {Record} 9 | */ 10 | function importEnvironmentVariablesFromObject(object) { 11 | const environmentVariables = Object.assign( 12 | {}, 13 | Object(object).environmentVariables || Object(object)['environment-variables'] 14 | ) 15 | 16 | for (const key in environmentVariables) { 17 | environmentVariables[key] = parse(environmentVariables[key], { ignoreUnknownWords: true }).nodes 18 | } 19 | 20 | return environmentVariables 21 | } 22 | 23 | /** 24 | * Import Custom Properties from JSON file 25 | * @param {string} from 26 | * @returns {Promise>} 27 | */ 28 | async function importEnvironmentVariablesFromJSONFile(from) { 29 | const object = await readJSON(path.resolve(from)) 30 | 31 | return importEnvironmentVariablesFromObject(object) 32 | } 33 | 34 | /** 35 | * Import Custom Properties from JS file 36 | * @param {string} from 37 | * @returns {Promise>} 38 | */ 39 | async function importEnvironmentVariablesFromJSFile(from) { 40 | const object = await import(path.resolve(from)) 41 | 42 | return importEnvironmentVariablesFromObject(object) 43 | } 44 | 45 | /** 46 | * Import Custom Properties from Sources 47 | * @param {(string|Function|Promise|{type:string,environmentVariables: Record, 'environment-variables': Record})[]} sources 48 | * @returns {Promise>} 49 | */ 50 | export default function importEnvironmentVariablesFromSources(sources) { 51 | return sources.map(source => { 52 | if (source instanceof Promise) { 53 | return source 54 | } else if (source instanceof Function) { 55 | return source() 56 | } 57 | 58 | // read the source as an object 59 | const opts = source === Object(source) ? source : { from: String(source) } 60 | 61 | // skip objects with Custom Properties 62 | if (opts.environmentVariables || opts['environment-variables']) { 63 | return opts 64 | } 65 | 66 | // source pathname 67 | const from = String(opts.from || '') 68 | 69 | // type of file being read from 70 | const type = (opts.type || path.extname(from).slice(1)).toLowerCase() 71 | 72 | return { type, from } 73 | }).reduce(async (environmentVariables, source) => { 74 | const { type, from } = await source 75 | 76 | if (type === 'js' || type === 'cjs') { 77 | return Object.assign(environmentVariables, await importEnvironmentVariablesFromJSFile(from)) 78 | } 79 | 80 | if (type === 'json') { 81 | return Object.assign(environmentVariables, await importEnvironmentVariablesFromJSONFile(from)) 82 | } 83 | 84 | return Object.assign(environmentVariables, importEnvironmentVariablesFromObject(await source)) 85 | }, {}) 86 | } 87 | 88 | /* Helper utilities 89 | /* ========================================================================== */ 90 | 91 | /** 92 | * @param {string} from 93 | * @returns {Promise} 94 | */ 95 | const readFile = from => new Promise((resolve, reject) => { 96 | fs.readFile(from, 'utf8', (error, result) => { 97 | if (error) { 98 | reject(error) 99 | } else { 100 | resolve(result) 101 | } 102 | }) 103 | }) 104 | 105 | const readJSON = async from => JSON.parse(await readFile(from)) 106 | -------------------------------------------------------------------------------- /src/lib/is-env-func.js: -------------------------------------------------------------------------------- 1 | // returns whether a node is a css env() function 2 | export default (node) => node && node.type === 'func' && node.name === 'env' 3 | -------------------------------------------------------------------------------- /src/lib/update-env-value.js: -------------------------------------------------------------------------------- 1 | import getFnValue from './get-fn-value' 2 | 3 | // update a node with an environment value 4 | export default (node, variables) => { 5 | // get the value of a css function as a string 6 | const value = getFnValue(node) 7 | 8 | if (typeof value === 'string' && value in variables) { 9 | node.replaceWith( 10 | ...asClonedArrayWithBeforeSpacing(variables[value], node.raws.before) 11 | ) 12 | } 13 | } 14 | 15 | // return an array with its nodes cloned, preserving the raw 16 | const asClonedArrayWithBeforeSpacing = (array, beforeSpacing) => { 17 | const clonedArray = asClonedArray(array, null) 18 | 19 | if (clonedArray[0]) { 20 | clonedArray[0].raws.before = beforeSpacing 21 | } 22 | 23 | return clonedArray 24 | } 25 | 26 | // return an array with its nodes cloned 27 | const asClonedArray = (array, parent) => array.map(node => asClonedNode(node, parent)) 28 | 29 | // return a cloned node 30 | const asClonedNode = (node, parent) => { 31 | const cloneNode = new node.constructor(node) 32 | 33 | for (const key in node) { 34 | if (key === 'parent') { 35 | cloneNode.parent = parent 36 | } else if (Object(node[key]).constructor === Array) { 37 | cloneNode[key] = asClonedArray(node.nodes, cloneNode) 38 | } else if (Object(node[key]).constructor === Object) { 39 | cloneNode[key] = Object.assign({}, node[key]) 40 | } 41 | } 42 | 43 | return cloneNode 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/walk-env-funcs.js: -------------------------------------------------------------------------------- 1 | import isEnvFunc from './is-env-func' 2 | 3 | // walks a node recursively and runs a function using its children 4 | export default function walk (node, fn) { 5 | node.nodes.slice(0).forEach(childNode => { 6 | if (childNode.nodes) { 7 | walk(childNode, fn) 8 | } 9 | 10 | if (isEnvFunc(childNode)) { 11 | fn(childNode) 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /test/basic.css: -------------------------------------------------------------------------------- 1 | body { 2 | order: 1; 3 | padding: env(--some-custom-padding); 4 | } 5 | 6 | @media (min-width: env(--another-custom-width)) { 7 | body { 8 | order: 2; 9 | } 10 | } 11 | 12 | @media (width > env(--another-custom-width)) { 13 | body { 14 | order: 3; 15 | } 16 | } 17 | 18 | html { 19 | background-image: url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==); 20 | } 21 | -------------------------------------------------------------------------------- /test/basic.expect.css: -------------------------------------------------------------------------------- 1 | body { 2 | order: 1; 3 | padding: env(--some-custom-padding); 4 | } 5 | 6 | @media (min-width: env(--another-custom-width)) { 7 | body { 8 | order: 2; 9 | } 10 | } 11 | 12 | @media (width > env(--another-custom-width)) { 13 | body { 14 | order: 3; 15 | } 16 | } 17 | 18 | html { 19 | background-image: url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==); 20 | } 21 | -------------------------------------------------------------------------------- /test/basic.import-is-empty.expect.css: -------------------------------------------------------------------------------- 1 | body { 2 | order: 1; 3 | padding: env(--some-custom-padding); 4 | } 5 | 6 | @media (min-width: env(--another-custom-width)) { 7 | body { 8 | order: 2; 9 | } 10 | } 11 | 12 | @media (width > env(--another-custom-width)) { 13 | body { 14 | order: 3; 15 | } 16 | } 17 | 18 | html { 19 | background-image: url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==); 20 | } 21 | -------------------------------------------------------------------------------- /test/basic.import.expect.css: -------------------------------------------------------------------------------- 1 | body { 2 | order: 1; 3 | padding: 20px; 4 | } 5 | 6 | @media (min-width: 600px) { 7 | body { 8 | order: 2; 9 | } 10 | } 11 | 12 | @media (width > 600px) { 13 | body { 14 | order: 3; 15 | } 16 | } 17 | 18 | html { 19 | background-image: url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==); 20 | } 21 | -------------------------------------------------------------------------------- /test/import-variables.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | environmentVariables: { 3 | '--some-custom-padding': '20px', 4 | '--another-custom-width': '600px' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /test/import-variables.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | environmentVariables: { 3 | '--some-custom-padding': '20px', 4 | '--another-custom-width': '600px' 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /test/import-variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "environment-variables": { 3 | "--some-custom-padding": "20px", 4 | "--another-custom-width": "600px" 5 | } 6 | } 7 | --------------------------------------------------------------------------------