├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── lib ├── apply-conditions.js ├── apply-raws.js ├── apply-styles.js ├── base64-encoded-import.js ├── data-url.js ├── format-import-prelude.js ├── load-content.js ├── parse-statements.js ├── parse-styles.js ├── process-content.js └── resolve-id.js ├── package.json ├── renovate.json └── test ├── custom-load.js ├── custom-resolve.js ├── custom-syntax-parser.js ├── data-url.js ├── filter.js ├── fixtures ├── charset-error.css ├── charset-import.css ├── charset-import.expected.css ├── custom-load.css ├── custom-load.expected.css ├── custom-resolve-array.css ├── custom-resolve-array.expected.css ├── custom-resolve-file.css ├── custom-resolve-file.expected.css ├── cyclical-skip-duplicates.css ├── cyclical-skip-duplicates.expected.css ├── cyclical.css ├── cyclical.expected.css ├── data-url.css ├── data-url.expected.css ├── duplicates.css ├── duplicates.expected.css ├── empty-and-useless.css ├── empty-and-useless.expected.css ├── filter-all.css ├── filter-all.expected.css ├── filter-ignore.css ├── filter-ignore.expected.css ├── filter-some.css ├── filter-some.expected.css ├── ignore.css ├── ignore.expected.css ├── import-css-sss.expected.css ├── import-css-sss.sss ├── import-sss-css.css ├── import-sss-css.expected.css ├── import-sss.css ├── import-sss.expected.css ├── imports │ ├── bar-decl.css │ ├── bar.css │ ├── bar │ │ ├── baz.css │ │ └── index.css │ ├── barbaz.css │ ├── baz.css │ ├── charset-content.css │ ├── charset.css │ ├── custom-resolve-1.css │ ├── custom-resolve-2.css │ ├── cyclical-a.css │ ├── cyclical-all.css │ ├── cyclical-b.css │ ├── cyclical-screen.css │ ├── data-url.css │ ├── empty.css │ ├── foo-decl.css │ ├── foo-duplicate.css │ ├── foo-duplicate2.css │ ├── foo-first.css │ ├── foo-layered.css │ ├── foo-recursive.css │ ├── foo-recursive.sss │ ├── foo-second.css │ ├── foo.css │ ├── foo │ │ ├── baz.css │ │ └── index.css │ ├── foobar.css │ ├── foobarbaz.css │ ├── ignore.css │ ├── import-missing.css │ ├── import-sugarbar.css │ ├── inline-comment.scss │ ├── layer-anonymous.css │ ├── layer-followed-by-ignore.css │ ├── layer-level-2-anonymous.css │ ├── layer-level-2.css │ ├── layer-level-3.css │ ├── layer-only.css │ ├── layer.css │ ├── media-combine-nested.css │ ├── media-content-level-2.css │ ├── media-content-level-3.css │ ├── media-import-level-2.css │ ├── media-import-level-3.css │ ├── media-query-level-2.css │ ├── media-query-level-3.css │ ├── media-query-screen.css │ ├── modules │ │ ├── empty │ │ │ ├── index.css │ │ │ └── package.json │ │ ├── main-js │ │ │ ├── index.css │ │ │ ├── main.js │ │ │ └── package.json │ │ ├── main-style │ │ │ ├── main.css │ │ │ ├── package.json │ │ │ └── style.css │ │ ├── main │ │ │ ├── main.css │ │ │ └── package.json │ │ ├── simple │ │ │ └── index.css │ │ ├── style │ │ │ ├── package.json │ │ │ └── style.css │ │ └── subpath │ │ │ ├── a.css │ │ │ ├── package.json │ │ │ └── style.css │ ├── proxy-file │ │ ├── index.css │ │ ├── proxy-file.css │ │ └── sub-directory │ │ │ ├── index.css │ │ │ └── proxy-file.css │ ├── qux.css │ ├── sugarbar.sss │ ├── supports-condition-level-2.css │ ├── supports-condition-level-3.css │ ├── syntax-error.css │ └── useless.css ├── layer-duplicate-anonymous-imports-skip.css ├── layer-duplicate-anonymous-imports-skip.expected.css ├── layer-duplicate-anonymous-imports.css ├── layer-duplicate-anonymous-imports.expected.css ├── layer-followed-by-ignore-with-conditions.css ├── layer-followed-by-ignore-with-conditions.expected.css ├── layer-followed-by-ignore-without-conditions.css ├── layer-followed-by-ignore-without-conditions.expected.css ├── layer-followed-by-ignore.css ├── layer-followed-by-ignore.expected.css ├── layer-import-atrules-anonymous.css ├── layer-import-atrules-anonymous.expected.css ├── layer-import-atrules.css ├── layer-import-atrules.expected.css ├── layer-rule-grouping.expected.css ├── layer-statement-with-conditions.css ├── layer-statement-with-conditions.expected.css ├── layer.css ├── layer.expected.css ├── media-charset.css ├── media-charset.expected.css ├── media-combine.css ├── media-combine.expected.css ├── media-content.css ├── media-content.expected.css ├── media-import.css ├── media-import.expected.css ├── media-query.css ├── media-query.expected.css ├── no-duplicate.css ├── no-duplicate.expected.css ├── order.css ├── order.expected.css ├── plugins.css ├── plugins.expected.css ├── resolve-custom-modules.css ├── resolve-custom-modules.expected.css ├── resolve-cwd.css ├── resolve-cwd.expected.css ├── resolve-from.css ├── resolve-from.expected.css ├── resolve-local-modules.css ├── resolve-local-modules.expected.css ├── resolve-modules.css ├── resolve-modules.expected.css ├── resolve-npm-subpackages.css ├── resolve-npm-subpackages.expected.css ├── resolve-path-cwd.css ├── resolve-path-cwd.expected.css ├── resolve-path-modules.css ├── resolve-path-modules.expected.css ├── resolve-path-root.css ├── resolve-path-root.expected.css ├── resolve-root.css ├── resolve-root.expected.css ├── same.css ├── same.expected.css ├── scss-parser.css ├── scss-parser.expected.css ├── scss-syntax.css ├── scss-syntax.expected.css ├── simple.css ├── simple.expected.css ├── subpath.css ├── subpath.expected.css ├── sugar-import-css.expected.css ├── sugar-import-css.sss ├── sugar.expected.css ├── sugar.sss ├── supports-condition-import.css ├── supports-condition-import.expected.css └── syntax-error.css ├── helpers ├── ast-checker.js └── check-fixture.js ├── import-events.js ├── import.js ├── layer.js ├── lint.js ├── media.js ├── node_modules ├── auto │ └── index.css ├── by-hand │ └── style.css ├── dep │ └── index.css ├── fake │ ├── main.css │ └── package.json ├── modularized-css │ └── foo │ │ ├── boo │ │ └── boomod.css │ │ └── index.css ├── nest │ ├── index.css │ └── node_modules │ │ └── ed │ │ └── index.css ├── use-dep-too │ └── index.css └── use-dep │ └── index.css ├── order.js ├── plugins.js ├── resolve.js ├── shared_modules ├── shared-auto │ └── index.css ├── shared-by-hand │ └── style.css ├── shared-dep │ └── index.css ├── shared-fake │ ├── main.css │ └── package.json ├── shared-nest │ ├── index.css │ └── shared_modules │ │ └── shared-ed │ │ └── index.css ├── shared-use-dep-too │ └── index.css └── shared-use-dep │ └── index.css ├── sourcemap ├── imported.css ├── in.css ├── index.html ├── out.css ├── out.css.map └── out.css.win.map ├── supports-condition.js ├── syntax-error.js └── web_modules ├── web-auto └── index.css ├── web-by-hand └── style.css ├── web-dep └── index.css ├── web-fake ├── main.css └── package.json ├── web-nest ├── index.css └── web_modules │ └── web-ed │ └── index.css ├── web-use-dep-too └── index.css └── web-use-dep └── index.css /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: master 6 | pull_request: 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [18.x, 20.x] 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v4 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: npm install 21 | - run: npm run ci 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test output 2 | test/fixtures/*.actual.css 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | /node_modules/ 40 | jspm_packages/ 41 | 42 | # Typescript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 16.1.0 / 2024-03-20 2 | 3 | - Allow bundling URLs with fragments (useful for Vite users) ([#560](https://github.com/postcss/postcss-import/issues/560), [#561](https://github.com/postcss/postcss-import/pull/561)) 4 | 5 | # 16.0.1 / 2024-02-14 6 | 7 | - Fix crash when handling some `@import`s with media conditions ([#557](https://github.com/postcss/postcss-import/issues/557), [#558](https://github.com/postcss/postcss-import/pull/558)) 8 | 9 | # 16.0.0 / 2024-01-02 10 | 11 | - **BREAKING:** Require Node.js v18+ ([#550](https://github.com/postcss/postcss-import/issues/550), [#551](https://github.com/postcss/postcss-import/pull/551)) 12 | - **BREAKING:** Signifigant rewrite, with small behavioral tweaks in a number of edge cases 13 | - Support for `@supports` conditional imports added ([#532](https://github.com/postcss/postcss-import/issues/532), [#548](https://github.com/postcss/postcss-import/pull/548)) 14 | - When `skipDuplicates` is `false`, handles import cycles correctly ([#462](https://github.com/postcss/postcss-import/issues/462), [#535](https://github.com/postcss/postcss-import/pull/535)) 15 | - Add `warnOnEmpty` option to allow disabling warnings for empty files ([#84](https://github.com/postcss/postcss-import/issues/84), [#541](https://github.com/postcss/postcss-import/pull/541)) 16 | - Use proper `node.error`s ([#518](https://github.com/postcss/postcss-import/issues/518), [#540](https://github.com/postcss/postcss-import/pull/540)) 17 | 18 | Huge thanks to [`@romainmenke`](https://github.com/romainmenke) for all the hard work he put into this release. 19 | 20 | # 15.1.0 / 2022-12-07 21 | 22 | - Add `data:` URL support (this is not useful for most consumers) ([#515](https://github.com/postcss/postcss-import/pull/515)) 23 | 24 | # 15.0.1 / 2022-12-01 25 | 26 | - Preserve layer in ignored `@import`s ([#510](https://github.com/postcss/postcss-import/issues/510), [#511](https://github.com/postcss/postcss-import/pull/511)) 27 | - Join media queries in the correct order ([#512](https://github.com/postcss/postcss-import/issues/512), [#513](https://github.com/postcss/postcss-import/pull/513)) 28 | 29 | # 15.0.0 / 2022-08-30 30 | 31 | - **BREAKING:** Require Node.js v14+ ([#497](https://github.com/postcss/postcss-import/pull/497)) 32 | - **BREAKING:** Require `nameLayer` option for handling anonymous layers ([#496](https://github.com/postcss/postcss-import/pull/496)) 33 | - Fix handling of `@media` queries inside layered imports ([#495](https://github.com/postcss/postcss-import/issues/495), [#496](https://github.com/postcss/postcss-import/pull/496)) 34 | 35 | # 14.1.0 / 2022-03-22 36 | 37 | - Add `@layer` support ([#483](https://github.com/postcss/postcss-import/pull/483)) 38 | 39 | # 14.0.2 / 2021-05-10 40 | 41 | - Remove remaining direct import of `postcss` package ([#455](https://github.com/postcss/postcss-import/issues/455), [#456](https://github.com/postcss/postcss-import/pull/456)) 42 | 43 | # 14.0.1 / 2021-03-31 44 | 45 | - Fix bug with `@charset` statements in media imports ([#448](https://github.com/postcss/postcss-import/issues/448), [#453](https://github.com/postcss/postcss-import/pull/453)) 46 | 47 | # 14.0.0 / 2020-12-14 48 | 49 | This release should not have breaking changes for the vast majority of users; only those with `@charset` statements in their CSS _may_ be affected. 50 | 51 | - **BREAKING:** Error if multiple incompatible `@charset` statements ([#447](https://github.com/postcss/postcss-import/pull/447)) 52 | - **BREAKING:** Warn if `@charset` statements are not at the top of files ([#447](https://github.com/postcss/postcss-import/pull/447)) 53 | - Fix handing of `@charset` ([#436](https://github.com/postcss/postcss-import/issues/436), [#447](https://github.com/postcss/postcss-import/pull/447)) 54 | 55 | # 13.0.0 / 2020-10-20 56 | 57 | - **BREAKING:** Require Node 10+ ([#429](https://github.com/postcss/postcss-import/pull/429)) 58 | - **BREAKING:** Upgrade to postcss v8 and require it as a `peerDependency` ([#427](https://github.com/postcss/postcss-import/issues/427), [#432](https://github.com/postcss/postcss-import/pull/432)) 59 | - Update dependencies 60 | 61 | # 12.0.1 / 2018-10-22 62 | 63 | - Add `plugin` property to dependency messages ([#379](https://github.com/postcss/postcss-import/issues/379), [#380](https://github.com/postcss/postcss-import/pull/380)) 64 | 65 | # 12.0.0 - 2018-08-04 66 | 67 | - Removed: Support for Node.js v4 68 | - Changed: Uses PostCSS v7 (https://github.com/postcss/postcss/releases/tag/7.0.0) 69 | 70 | # 11.1.0 - 2018-02-10 71 | 72 | - Added: `filter` option 73 | 74 | # 11.0.0 - 2017-09-16 75 | 76 | - Changed: A syntax error in an imported file now throws an error instead of just warning ([#264](https://github.com/postcss/postcss-import/issues/264)) 77 | - Changed: Symlink handling to be consistent with Node.js `require` ([#300](https://github.com/postcss/postcss-import/pull/300)) 78 | 79 | # 10.0.0 - 2017-05-12 80 | 81 | - Removed: Support for Node.js versions less than 4.5.x ([#283](https://github.com/postcss/postcss-import/pull/283)) 82 | - Changed: Upgraded to Postcss v6 ([#283](https://github.com/postcss/postcss-import/pull/283)) 83 | - Removed: jspm support ([#283](https://github.com/postcss/postcss-import/pull/283)) 84 | - Removed: deprecated `addDependencyTo` option 85 | - Removed: `onImport` option 86 | - Changed: Doesn't depend on promise-each ([#281](https://github.com/postcss/postcss-import/pull/281)) 87 | 88 | # 9.1.0 - 2017-01-10 89 | 90 | - Added: `addModulesDirectories` option ([#256](https://github.com/postcss/postcss-import/pull/256)) 91 | 92 | # 9.0.0 - 2016-12-02 93 | 94 | - Removed: `transform` option 95 | ([#250](https://github.com/postcss/postcss-import/pull/250)) 96 | - Removed: `pkg-resolve` is no longer a dependency; this should fix some issues 97 | with webpack. jspm users must manually install `pkg-resolve` if they want to 98 | load jspm modules (see https://github.com/postcss/postcss-import#jspm-usage 99 | for more info) ([#243](https://github.com/postcss/postcss-import/pull/243)) 100 | - Changed: If a file is not found, it will now throw an error instead of just 101 | raising a warning ([#247](https://github.com/postcss/postcss-import/pull/247)) 102 | - Changed: If a custom resolver does not return an absolute path, the default 103 | resolver will be applied to the returned path. 104 | ([#249](https://github.com/postcss/postcss-import/pull/249)) 105 | - Changed: postcss-import will try to guess the correct parser for imported 106 | files, based on the file extension. 107 | ([#245](https://github.com/postcss/postcss-import/pull/245)) 108 | - Changed: Deprecated `addDependencyTo` option, it is not needed if using 109 | postcss-loader >= v1.0.0 110 | ([#251](https://github.com/postcss/postcss-import/pull/251)) 111 | 112 | # 8.2.0 - 2016-11-09 113 | 114 | - Fixed: Warn about all `@import`s after other CSS declarations 115 | ([#240](https://github.com/postcss/postcss-import/pull/240)) 116 | - Added: `dependency` message 117 | ([#241](https://github.com/postcss/postcss-import/pull/241)) 118 | 119 | # 8.1.3 - 2016-11-03 120 | 121 | - Fixed: Nested import ordering 122 | ([#236](https://github.com/postcss/postcss-import/pull/236) - @RyanZim) 123 | 124 | # 8.1.2 - 2016-05-07 125 | 126 | - Fixed: prevent JSPM to throw unrecoverable error 127 | ([#205](https://github.com/postcss/postcss-import/pull/205)) 128 | 129 | # 8.1.1 - 2016-05-04 130 | 131 | - Fixed: JSPM support 132 | ([#194](https://github.com/postcss/postcss-import/pull/194)) 133 | 134 | # 8.1.0 - 2016-04-04 135 | 136 | - Added: JSPM browser field 137 | ([#186](https://github.com/postcss/postcss-import/pull/186)) 138 | 139 | # 8.0.2 - 2015-01-27 140 | 141 | - Fixed: Comments between imports statements are ignored 142 | ([#164](https://github.com/postcss/postcss-import/pull/164)) 143 | 144 | # 8.0.1 - 2015-01-27 145 | 146 | - Fixed: missing "lib" folder 147 | ([#161](https://github.com/postcss/postcss-import/issues/161)) 148 | 149 | # 8.0.0 - 2015-01-27 150 | 151 | **All imports statements must be at the top of your file now, per CSS specification.** 152 | You should use [postcss-reporter](https://github.com/postcss/postcss-reporter) to see the warnings raised. 153 | 154 | - Removed: async mode/option (now async by default) 155 | ([#107](https://github.com/postcss/postcss-import/pull/107)) 156 | - Removed: "bower_components" not supported by default anymore, 157 | use "path" option to add it back 158 | - Removed: `encoding` option. Encoding can be specified in custom `load` option 159 | 160 | ```js 161 | postcssImport({ 162 | load: function(filename) { 163 | return fs.readFileSync(filename, "utf-8") 164 | } 165 | }) 166 | ``` 167 | ([#144](https://github.com/postcss/postcss-import/pull/144)) 168 | 169 | - Removed: glob support 170 | ([#146](https://github.com/postcss/postcss-import/pull/146)) 171 | 172 | Globs can be implemented with custom `resolve` option 173 | 174 | ```js 175 | postcssImport({ 176 | resolve: function(id, base) { 177 | return glob.sync(path.join(base, id)) 178 | } 179 | }) 180 | ``` 181 | 182 | ([#116](https://github.com/postcss/postcss-import/pull/116)) 183 | - Changed: custom resolve has more responsibility for paths resolving. 184 | See [resolve option](https://github.com/postcss/postcss-import#resolve) 185 | for more information about this change 186 | ([#116](https://github.com/postcss/postcss-import/pull/116)) 187 | - Changed: support promise in `transform` option and `undefined` result will be 188 | skipped 189 | ([#147](https://github.com/postcss/postcss-import/pull/147)) 190 | - Changed: `options.plugins` are applied to unprocessed ast before imports 191 | detecting 192 | ([157](https://github.com/postcss/postcss-import/pull/157)) 193 | - Added: custom resolve function can return array of paths 194 | ([#120](https://github.com/postcss/postcss-import/pull/120)) 195 | - Added: custom syntax in imported files support 196 | ([#130](https://github.com/postcss/postcss-import/pull/130)) 197 | - Added: support custom `load` option 198 | ([#144](https://github.com/postcss/postcss-import/pull/144)) 199 | - Added: detect css extension in package.json `main` field 200 | ([153](https://github.com/postcss/postcss-import/pull/153)) 201 | 202 | **Note:** 203 | _If you miss options/default behavior (glob etc), a new plugin will handle all 204 | those things. 205 | Please follow issue [#145](https://github.com/postcss/postcss-import/issues/145) 206 | _ 207 | 208 | # 7.1.3 - 2015-11-05 209 | 210 | - Fixed: ensure node 0.12 compatibility, round 2 211 | ([#93](https://github.com/postcss/postcss-import/pull/93)) 212 | 213 | # 7.1.2 - 2015-11-05 214 | 215 | - Fixed: performance issue because of cloned options 216 | ([#90](https://github.com/postcss/postcss-import/pull/90)) 217 | 218 | # 7.1.1 - 2015-11-05 219 | 220 | - Added: ensure node 0.12 compatibility 221 | 222 | # 7.0.0 - 2015-08-25 223 | 224 | - Removed: compatibility with postcss v4.x 225 | ([#75](https://github.com/postcss/postcss-import/pull/75)) 226 | - Added: compatibility with postcss v5.x 227 | ([#76](https://github.com/postcss/postcss-import/pull/76)) 228 | - Added: lighter package by upgrading some dependencies 229 | ([#73](https://github.com/postcss/postcss-import/issues/73)) 230 | 231 | # 6.2.0 - 2015-07-21 232 | 233 | - Added: `skipDuplicates` option now allows you to **not** skip duplicated files 234 | ([#67](https://github.com/postcss/postcss-import/issues/67)) 235 | 236 | # 6.1.1 - 2015-07-07 237 | 238 | - Fixed: Prevent mutability issue, round 2 239 | ([#44](https://github.com/postcss/postcss-import/issues/44)) 240 | - Added: `plugins` option, to run some postcss plugin on imported files 241 | ([#55](https://github.com/postcss/postcss-import/issues/55)) 242 | - Added: `bower_components` is now part of the default paths 243 | ([#66](https://github.com/postcss/postcss-import/issues/66)) 244 | - Added: `async` option allow to use enable PostCSS async API usage. 245 | Note that it's not enabling async fs read yet. It has been added to fix breaking 246 | change introduced by 6.1.0. 247 | 248 | # 6.1.0 - 2015-07-07 **YANKED** 249 | 250 | _This release was not respecting semver and introduced a major breaking change. 251 | It has been unpublished for now._ 252 | 253 | # 6.0.0 - 2015-06-17 254 | 255 | - Changed: warnings messages are now using postcss message api (4.1.x) 256 | - Added: warning when a import statement has not been closed correctly 257 | ([#42](https://github.com/postcss/postcss-import/issues/42)) 258 | 259 | # 5.2.2 - 2015-04-19 260 | 261 | - Fixed: globbed imports work for module directories ([#37](https://github.com/postcss/postcss-import/pull/37)) 262 | 263 | # 5.2.1 - 2015-04-17 264 | 265 | - Fixed: glob import now works with single quote `@import` ([#36](https://github.com/postcss/postcss-import/pull/36)) 266 | 267 | # 5.2.0 - 2015-04-15 268 | 269 | - Added: [glob](https://www.npmjs.com/package/glob) pattern are now supported if `glob` option is set to true ([#34](https://github.com/postcss/postcss-import/pull/34)) 270 | - Added: plugin can now be added to PostCSS without calling it as a function ([#27](https://github.com/postcss/postcss-import/pull/27)) 271 | 272 | # 5.1.1 - 2015-04-10 273 | 274 | - Fixed: regression of 5.1.0: files which only contain same @import rules were skip ([#31](https://github.com/postcss/postcss-import/issues/31)) 275 | 276 | # 5.1.0 - 2015-03-27 277 | 278 | - Added: files with the same content will only be imported once. Previously, only the full path was used to determine if a file has already been imported in a given scope. 279 | Now, we also test create a hash with the content of the file to check if a file with the same content has not already been imported. 280 | This might be usefull if some modules you import are importing the same library from different places (eg: normalize might be as dep for several modules located in different places in `node_modules`) 281 | ([#29](https://github.com/postcss/postcss-import/pull/28)) 282 | 283 | # 5.0.3 - 2015-02-16 284 | 285 | - Fixed: regression of 5.0.2: AST parent references were not updated ([#25](https://github.com/postcss/postcss-import/issues/25)) 286 | 287 | # 5.0.2 - 2015-02-14 288 | 289 | - Fixed: indentation and code style are now preserved ([#20](https://github.com/postcss/postcss-import/issues/20)) 290 | 291 | # 5.0.1 - 2015-02-13 292 | 293 | - Fixed: breaking bug with remote stylesheets ([#21](https://github.com/postcss/postcss-import/issues/21) & [#22](https://github.com/postcss/postcss-import/issues/22)) 294 | 295 | # 5.0.0 - 2015-01-26 296 | 297 | - Added: compatibility with postcss v4.x 298 | - Removed: compatibility with postcss v3.x 299 | - Fixed: relative imports (./ and ../) should work using `path` option only (no need for `from`) ([#14](https://github.com/postcss/postcss-import/issues/14)) 300 | 301 | # 4.1.1 - 2015-01-05 302 | 303 | - Fixed: irregular whitespace that throw syntax error in some environnements 304 | 305 | # 4.1.0 - 2014-12-12 306 | 307 | - Added: `web_modules` is now in module directories that are used to resolve `@import` ([#13](https://github.com/postcss/postcss-import/issues/13)). 308 | 309 | # 4.0.0 - 2014-12-11 310 | 311 | - Added: windows compatibility (by building on AppVeyor) 312 | - Added: `root` option 313 | 314 | # 3.2.0 - 2014-11-24 315 | 316 | - Added: `onImport` callback offers a way to get list of imported files ([ref](https://github.com/postcss/postcss-import/issues/9)) 317 | 318 | # 3.1.0 - 2014-11-24 319 | 320 | - Added: ability to consume local modules (fix [#12](https://github.com/postcss/postcss-import/issues/12)) 321 | 322 | # 3.0.0 - 2014-11-21 323 | 324 | - Added: ability to consume node modules ([ref](https://github.com/postcss/postcss-import/issues/7)). 325 | This means you don't have to add `node_modules` in the path anymore (or using `@import "../node_modules/..."`). 326 | Also, `index.css` can be ommited. 327 | 328 | This means something like this 329 | 330 | ```css 331 | @import "../node_modules/my-css-on-npm/index.css"; 332 | ``` 333 | 334 | can be written like this 335 | 336 | ```css 337 | @import "my-css-on-npm"; 338 | ``` 339 | 340 | Dependencies of dependencies should be resolved as well. 341 | 342 | _Note that npm resolution is done after the default local behavior._ 343 | 344 | - Changed: When importing a file multiple times in the same scope (same level of media queries), file will only be imported the first time. 345 | This is done to avoid having multiples outputs of a npm dep used multiples times in different modules. 346 | 347 | # 2.0.0 - 2014-11-12 348 | 349 | - Added: compatibility with postcss v3.x 350 | - Removed: compatibility with postcss v2.x 351 | 352 | # 1.0.3 - 2014-10-29 353 | 354 | - Fixed: relative import path stack 355 | 356 | # 1.0.2 - 2014-09-16 357 | 358 | - Added: Move ignored import at top & adjust related media queries, to make them work (fix [#2](https://github.com/postcss/postcss-import/issues/2)) 359 | - Added: Ignore scheme-relative absolute URLs 360 | - Removed: `parse-import` module dependency 361 | 362 | # 1.0.1 - 2014-08-26 363 | 364 | - Fixed: GNU message format 365 | - Added: Support empty files ([cssnext/#24](https://github.com/putaindecode/cssnext/issues/24)) 366 | 367 | # 1.0.0 - 2014-08-10 368 | 369 | ✨ First release based on [rework-import](https://github.com/reworkcss/rework-import) v1.2.0 (mainly for fixtures) 370 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Maxime Thirouin, Jason Campbell & Kevin Mårtensson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-import 2 | 3 | [![Build](https://img.shields.io/travis/postcss/postcss-import/master)](https://travis-ci.org/postcss/postcss-import) 4 | [![Version](https://img.shields.io/npm/v/postcss-import)](https://github.com/postcss/postcss-import/blob/master/CHANGELOG.md) 5 | [![postcss compatibility](https://img.shields.io/npm/dependency-version/postcss-import/peer/postcss)](https://postcss.org/) 6 | 7 | > [PostCSS](https://github.com/postcss/postcss) plugin to transform `@import` 8 | rules by inlining content. 9 | 10 | This plugin can consume local files, node modules or web_modules. 11 | To resolve path of an `@import` rule, it can look into root directory 12 | (by default `process.cwd()`), `web_modules`, `node_modules` 13 | or local modules. 14 | _When importing a module, it will look for `index.css` or file referenced in 15 | `package.json` in the `style` or `main` fields._ 16 | You can also provide manually multiples paths where to look at. 17 | 18 | **Notes:** 19 | 20 | - **This plugin should probably be used as the first plugin of your list. 21 | This way, other plugins will work on the AST as if there were only a single file 22 | to process, and will probably work as you can expect**. 23 | - Running [postcss-url](https://github.com/postcss/postcss-url) after 24 | postcss-import in your plugin chain will allow you to adjust assets `url()` (or 25 | even inline them) after inlining imported files. 26 | - In order to optimize output, **this plugin will only import a file once** on 27 | a given scope (root, media query...). 28 | Tests are made from the path & the content of imported files (using a hash 29 | table). 30 | If this behavior is not what you want, look at `skipDuplicates` option 31 | - If you are looking for **Glob Imports**, you can use [postcss-import-ext-glob](https://github.com/dimitrinicolas/postcss-import-ext-glob) to extend postcss-import. 32 | - If you want to import remote sources, you can use [postcss-import-url](https://github.com/unlight/postcss-import-url) with its `dataUrls` plugin option to extend postcss-import. 33 | - Imports which are not modified (by `options.filter` or because they are remote 34 | imports) are moved to the top of the output. 35 | - **This plugin attempts to follow the CSS `@import` spec**; `@import` 36 | statements must precede all other statements (besides `@charset`). 37 | 38 | ## Installation 39 | 40 | ```console 41 | $ npm install -D postcss-import 42 | ``` 43 | 44 | ## Usage 45 | 46 | Unless your stylesheet is in the same place where you run postcss 47 | (`process.cwd()`), you will need to use `from` option to make relative imports 48 | work. 49 | 50 | ```js 51 | // dependencies 52 | const fs = require("fs") 53 | const postcss = require("postcss") 54 | const atImport = require("postcss-import") 55 | 56 | // css to be processed 57 | const css = fs.readFileSync("css/input.css", "utf8") 58 | 59 | // process css 60 | postcss() 61 | .use(atImport()) 62 | .process(css, { 63 | // `from` option is needed here 64 | from: "css/input.css" 65 | }) 66 | .then((result) => { 67 | const output = result.css 68 | 69 | console.log(output) 70 | }) 71 | ``` 72 | 73 | `css/input.css`: 74 | 75 | ```css 76 | /* remote urls are preserved */ 77 | @import "https://example.com/styles.css"; 78 | 79 | /* can consume `node_modules`, `web_modules` or local modules */ 80 | @import "cssrecipes-defaults"; /* == @import "../node_modules/cssrecipes-defaults/index.css"; */ 81 | @import "normalize.css"; /* == @import "../node_modules/normalize.css/normalize.css"; */ 82 | 83 | @import "foo.css"; /* relative to css/ according to `from` option above */ 84 | 85 | /* all standard notations of the "url" value are supported */ 86 | @import url(foo-1.css); 87 | @import url("foo-2.css"); 88 | 89 | @import "bar.css" (min-width: 25em); 90 | 91 | @import 'baz.css' layer(baz-layer); 92 | 93 | body { 94 | background: black; 95 | } 96 | ``` 97 | 98 | will give you: 99 | 100 | ```css 101 | @import "https://example.com/styles.css"; 102 | 103 | /* ... content of ../node_modules/cssrecipes-defaults/index.css */ 104 | /* ... content of ../node_modules/normalize.css/normalize.css */ 105 | 106 | /* ... content of css/foo.css */ 107 | 108 | /* ... content of css/foo-1.css */ 109 | /* ... content of css/foo-2.css */ 110 | 111 | @media (min-width: 25em) { 112 | /* ... content of css/bar.css */ 113 | } 114 | 115 | @layer baz-layer { 116 | /* ... content of css/baz.css */ 117 | } 118 | 119 | body { 120 | background: black; 121 | } 122 | ``` 123 | 124 | Checkout the [tests](test) for more examples. 125 | 126 | ### Options 127 | 128 | #### `filter` 129 | Type: `Function` 130 | Default: `() => true` 131 | 132 | Only transform imports for which the test function returns `true`. Imports for 133 | which the test function returns `false` will be left as is. The function gets 134 | the path to import as an argument and should return a boolean. 135 | 136 | #### `root` 137 | 138 | Type: `String` 139 | Default: `process.cwd()` or _dirname of 140 | [the postcss `from`](https://github.com/postcss/postcss#node-source)_ 141 | 142 | Define the root where to resolve path (eg: place where `node_modules` are). 143 | Should not be used that much. 144 | _Note: nested `@import` will additionally benefit of the relative dirname of 145 | imported files._ 146 | 147 | #### `path` 148 | 149 | Type: `String|Array` 150 | Default: `[]` 151 | 152 | A string or an array of paths in where to look for files. 153 | 154 | #### `plugins` 155 | 156 | Type: `Array` 157 | Default: `undefined` 158 | 159 | An array of plugins to be applied on each imported files. 160 | 161 | #### `resolve` 162 | 163 | Type: `Function` 164 | Default: `null` 165 | 166 | You can provide a custom path resolver with this option. This function gets 167 | `(id, basedir, importOptions, astNode)` arguments and should return a path, an array of 168 | paths or a promise resolving to the path(s). If you do not return an absolute 169 | path, your path will be resolved to an absolute path using the default 170 | resolver. 171 | You can use [resolve](https://github.com/substack/node-resolve) for this. 172 | 173 | #### `load` 174 | 175 | Type: `Function` 176 | Default: null 177 | 178 | You can overwrite the default loading way by setting this option. 179 | This function gets `(filename, importOptions)` arguments and returns content or 180 | promised content. 181 | 182 | #### `skipDuplicates` 183 | 184 | Type: `Boolean` 185 | Default: `true` 186 | 187 | By default, similar files (based on the same content) are being skipped. 188 | It's to optimize output and skip similar files like `normalize.css` for example. 189 | If this behavior is not what you want, just set this option to `false` to 190 | disable it. 191 | 192 | #### `addModulesDirectories` 193 | 194 | Type: `Array` 195 | Default: `[]` 196 | 197 | An array of folder names to add to [Node's resolver](https://github.com/substack/node-resolve). 198 | Values will be appended to the default resolve directories: 199 | `["node_modules", "web_modules"]`. 200 | 201 | This option is only for adding additional directories to default resolver. If 202 | you provide your own resolver via the `resolve` configuration option above, then 203 | this value will be ignored. 204 | 205 | #### `warnOnEmpty` 206 | 207 | Type: `Boolean` 208 | Default: `true` 209 | 210 | By default `postcss-import` warns when an empty file is imported. 211 | Set this option to `false` to disable this warning. 212 | 213 | #### Example with some options 214 | 215 | ```js 216 | const postcss = require("postcss") 217 | const atImport = require("postcss-import") 218 | 219 | postcss() 220 | .use(atImport({ 221 | path: ["src/css"], 222 | })) 223 | .process(cssString) 224 | .then((result) => { 225 | const { css } = result 226 | }) 227 | ``` 228 | 229 | ## `dependency` Message Support 230 | 231 | `postcss-import` adds a message to `result.messages` for each `@import`. Messages are in the following format: 232 | 233 | ``` 234 | { 235 | type: 'dependency', 236 | file: absoluteFilePath, 237 | parent: fileContainingTheImport 238 | } 239 | ``` 240 | 241 | This is mainly for use by postcss runners that implement file watching. 242 | 243 | --- 244 | 245 | ## CONTRIBUTING 246 | 247 | * ⇄ Pull requests and ★ Stars are always welcome. 248 | * For bugs and feature requests, please create an issue. 249 | * Pull requests must be accompanied by passing automated tests (`$ npm test`). 250 | 251 | ## [Changelog](CHANGELOG.md) 252 | 253 | ## [License](LICENSE) 254 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // builtin tooling 3 | const path = require("path") 4 | 5 | // internal tooling 6 | const applyConditions = require("./lib/apply-conditions") 7 | const applyRaws = require("./lib/apply-raws") 8 | const applyStyles = require("./lib/apply-styles") 9 | const loadContent = require("./lib/load-content") 10 | const parseStyles = require("./lib/parse-styles") 11 | const resolveId = require("./lib/resolve-id") 12 | 13 | function AtImport(options) { 14 | options = { 15 | root: process.cwd(), 16 | path: [], 17 | skipDuplicates: true, 18 | resolve: resolveId, 19 | load: loadContent, 20 | plugins: [], 21 | addModulesDirectories: [], 22 | warnOnEmpty: true, 23 | ...options, 24 | } 25 | 26 | options.root = path.resolve(options.root) 27 | 28 | // convert string to an array of a single element 29 | if (typeof options.path === "string") options.path = [options.path] 30 | 31 | if (!Array.isArray(options.path)) options.path = [] 32 | 33 | options.path = options.path.map(p => path.resolve(options.root, p)) 34 | 35 | return { 36 | postcssPlugin: "postcss-import", 37 | async Once(styles, { result, atRule, postcss }) { 38 | const state = { 39 | importedFiles: {}, 40 | hashFiles: {}, 41 | } 42 | 43 | if (styles.source?.input?.file) { 44 | state.importedFiles[styles.source.input.file] = {} 45 | } 46 | 47 | if (options.plugins && !Array.isArray(options.plugins)) { 48 | throw new Error("plugins option must be an array") 49 | } 50 | 51 | const bundle = await parseStyles( 52 | result, 53 | styles, 54 | options, 55 | state, 56 | [], 57 | [], 58 | postcss, 59 | ) 60 | 61 | applyRaws(bundle) 62 | applyConditions(bundle, atRule) 63 | applyStyles(bundle, styles) 64 | }, 65 | } 66 | } 67 | 68 | AtImport.postcss = true 69 | 70 | module.exports = AtImport 71 | -------------------------------------------------------------------------------- /lib/apply-conditions.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const base64EncodedConditionalImport = require("./base64-encoded-import") 4 | 5 | module.exports = function applyConditions(bundle, atRule) { 6 | const firstImportStatementIndex = bundle.findIndex( 7 | stmt => stmt.type === "import", 8 | ) 9 | const lastImportStatementIndex = bundle.findLastIndex( 10 | stmt => stmt.type === "import", 11 | ) 12 | 13 | bundle.forEach((stmt, index) => { 14 | if (stmt.type === "charset" || stmt.type === "warning") { 15 | return 16 | } 17 | 18 | if ( 19 | stmt.type === "layer" && 20 | ((index < lastImportStatementIndex && stmt.conditions?.length) || 21 | (index > firstImportStatementIndex && index < lastImportStatementIndex)) 22 | ) { 23 | stmt.type = "import" 24 | stmt.node = stmt.node.clone({ 25 | name: "import", 26 | params: base64EncodedConditionalImport( 27 | `'data:text/css;base64,${Buffer.from(stmt.node.toString()).toString( 28 | "base64", 29 | )}'`, 30 | stmt.conditions, 31 | ), 32 | }) 33 | 34 | return 35 | } 36 | 37 | if (!stmt.conditions?.length) { 38 | return 39 | } 40 | 41 | if (stmt.type === "import") { 42 | stmt.node.params = base64EncodedConditionalImport( 43 | stmt.fullUri, 44 | stmt.conditions, 45 | ) 46 | return 47 | } 48 | 49 | let nodes 50 | let parent 51 | if (stmt.type === "layer") { 52 | nodes = [stmt.node] 53 | parent = stmt.node.parent 54 | } else { 55 | nodes = stmt.nodes 56 | parent = nodes[0].parent 57 | } 58 | 59 | const atRules = [] 60 | 61 | // Convert conditions to at-rules 62 | for (const condition of stmt.conditions) { 63 | if (typeof condition.media !== "undefined") { 64 | const mediaNode = atRule({ 65 | name: "media", 66 | params: condition.media, 67 | source: parent.source, 68 | }) 69 | 70 | atRules.push(mediaNode) 71 | } 72 | 73 | if (typeof condition.supports !== "undefined") { 74 | const supportsNode = atRule({ 75 | name: "supports", 76 | params: `(${condition.supports})`, 77 | source: parent.source, 78 | }) 79 | 80 | atRules.push(supportsNode) 81 | } 82 | 83 | if (typeof condition.layer !== "undefined") { 84 | const layerNode = atRule({ 85 | name: "layer", 86 | params: condition.layer, 87 | source: parent.source, 88 | }) 89 | 90 | atRules.push(layerNode) 91 | } 92 | } 93 | 94 | // Add nodes to AST 95 | const outerAtRule = atRules.shift() 96 | const innerAtRule = atRules.reduce((previous, next) => { 97 | previous.append(next) 98 | return next 99 | }, outerAtRule) 100 | 101 | parent.insertBefore(nodes[0], outerAtRule) 102 | 103 | // remove nodes 104 | nodes.forEach(node => { 105 | node.parent = undefined 106 | }) 107 | 108 | // better output 109 | nodes[0].raws.before = nodes[0].raws.before || "\n" 110 | 111 | // wrap new rules with media query and/or layer at rule 112 | innerAtRule.append(nodes) 113 | 114 | stmt.type = "nodes" 115 | stmt.nodes = [outerAtRule] 116 | delete stmt.node 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /lib/apply-raws.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = function applyRaws(bundle) { 4 | bundle.forEach((stmt, index) => { 5 | if (index === 0) return 6 | 7 | if (stmt.parent) { 8 | const { before } = stmt.parent.node.raws 9 | if (stmt.type === "nodes") stmt.nodes[0].raws.before = before 10 | else stmt.node.raws.before = before 11 | } else if (stmt.type === "nodes") { 12 | stmt.nodes[0].raws.before = stmt.nodes[0].raws.before || "\n" 13 | } 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /lib/apply-styles.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = function applyStyles(bundle, styles) { 4 | styles.nodes = [] 5 | 6 | // Strip additional statements. 7 | bundle.forEach(stmt => { 8 | if (["charset", "import", "layer"].includes(stmt.type)) { 9 | stmt.node.parent = undefined 10 | styles.append(stmt.node) 11 | } else if (stmt.type === "nodes") { 12 | stmt.nodes.forEach(node => { 13 | node.parent = undefined 14 | styles.append(node) 15 | }) 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /lib/base64-encoded-import.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const formatImportPrelude = require("./format-import-prelude") 4 | 5 | // Base64 encode an import with conditions 6 | // The order of conditions is important and is interleaved with cascade layer declarations 7 | // Each group of conditions and cascade layers needs to be interpreted in order 8 | // To achieve this we create a list of base64 encoded imports, where each import contains a stylesheet with another import. 9 | // Each import can define a single group of conditions and a single cascade layer. 10 | module.exports = function base64EncodedConditionalImport(prelude, conditions) { 11 | if (!conditions?.length) return prelude 12 | 13 | conditions.reverse() 14 | const first = conditions.pop() 15 | let params = `${prelude} ${formatImportPrelude( 16 | first.layer, 17 | first.media, 18 | first.supports, 19 | )}` 20 | 21 | for (const condition of conditions) { 22 | params = `'data:text/css;base64,${Buffer.from(`@import ${params}`).toString( 23 | "base64", 24 | )}' ${formatImportPrelude( 25 | condition.layer, 26 | condition.media, 27 | condition.supports, 28 | )}` 29 | } 30 | 31 | return params 32 | } 33 | -------------------------------------------------------------------------------- /lib/data-url.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const anyDataURLRegexp = /^data:text\/css(?:;(base64|plain))?,/i 4 | const base64DataURLRegexp = /^data:text\/css;base64,/i 5 | const plainDataURLRegexp = /^data:text\/css;plain,/i 6 | 7 | function isValid(url) { 8 | return anyDataURLRegexp.test(url) 9 | } 10 | 11 | function contents(url) { 12 | if (base64DataURLRegexp.test(url)) { 13 | // "data:text/css;base64,".length === 21 14 | return Buffer.from(url.slice(21), "base64").toString() 15 | } 16 | 17 | if (plainDataURLRegexp.test(url)) { 18 | // "data:text/css;plain,".length === 20 19 | return decodeURIComponent(url.slice(20)) 20 | } 21 | 22 | // "data:text/css,".length === 14 23 | return decodeURIComponent(url.slice(14)) 24 | } 25 | 26 | module.exports = { 27 | isValid, 28 | contents, 29 | } 30 | -------------------------------------------------------------------------------- /lib/format-import-prelude.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = function formatImportPrelude(layer, media, supports) { 4 | const parts = [] 5 | 6 | if (typeof layer !== "undefined") { 7 | let layerParams = "layer" 8 | if (layer) { 9 | layerParams = `layer(${layer})` 10 | } 11 | 12 | parts.push(layerParams) 13 | } 14 | 15 | if (typeof supports !== "undefined") { 16 | parts.push(`supports(${supports})`) 17 | } 18 | 19 | if (typeof media !== "undefined") { 20 | parts.push(media) 21 | } 22 | 23 | return parts.join(" ") 24 | } 25 | -------------------------------------------------------------------------------- /lib/load-content.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const readCache = require("read-cache") 4 | const dataURL = require("./data-url") 5 | 6 | module.exports = function loadContent(filename) { 7 | if (dataURL.isValid(filename)) { 8 | return dataURL.contents(filename) 9 | } 10 | 11 | return readCache(filename, "utf-8") 12 | } 13 | -------------------------------------------------------------------------------- /lib/parse-statements.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | // external tooling 4 | const valueParser = require("postcss-value-parser") 5 | 6 | // extended tooling 7 | const { stringify } = valueParser 8 | 9 | module.exports = function parseStatements(result, styles, conditions, from) { 10 | const statements = [] 11 | let nodes = [] 12 | let encounteredNonImportNodes = false 13 | 14 | styles.each(node => { 15 | let stmt 16 | if (node.type === "atrule") { 17 | if (node.name === "import") 18 | stmt = parseImport(result, node, conditions, from) 19 | else if (node.name === "charset") 20 | stmt = parseCharset(result, node, conditions, from) 21 | else if ( 22 | node.name === "layer" && 23 | !encounteredNonImportNodes && 24 | !node.nodes 25 | ) 26 | stmt = parseLayer(result, node, conditions, from) 27 | } else if (node.type !== "comment") { 28 | encounteredNonImportNodes = true 29 | } 30 | 31 | if (stmt) { 32 | if (nodes.length) { 33 | statements.push({ 34 | type: "nodes", 35 | nodes, 36 | conditions: [...conditions], 37 | from, 38 | }) 39 | nodes = [] 40 | } 41 | statements.push(stmt) 42 | } else nodes.push(node) 43 | }) 44 | 45 | if (nodes.length) { 46 | statements.push({ 47 | type: "nodes", 48 | nodes, 49 | conditions: [...conditions], 50 | from, 51 | }) 52 | } 53 | 54 | return statements 55 | } 56 | 57 | function parseCharset(result, atRule, conditions, from) { 58 | if (atRule.prev()) { 59 | return result.warn("@charset must precede all other statements", { 60 | node: atRule, 61 | }) 62 | } 63 | return { 64 | type: "charset", 65 | node: atRule, 66 | conditions: [...conditions], 67 | from, 68 | } 69 | } 70 | 71 | function parseImport(result, atRule, conditions, from) { 72 | let prev = atRule.prev() 73 | 74 | // `@import` statements may follow other `@import` statements. 75 | if (prev) { 76 | do { 77 | if ( 78 | prev.type === "comment" || 79 | (prev.type === "atrule" && prev.name === "import") 80 | ) { 81 | prev = prev.prev() 82 | continue 83 | } 84 | 85 | break 86 | } while (prev) 87 | } 88 | 89 | // All `@import` statements may be preceded by `@charset` or `@layer` statements. 90 | // But the `@import` statements must be consecutive. 91 | if (prev) { 92 | do { 93 | if ( 94 | prev.type === "comment" || 95 | (prev.type === "atrule" && 96 | (prev.name === "charset" || (prev.name === "layer" && !prev.nodes))) 97 | ) { 98 | prev = prev.prev() 99 | continue 100 | } 101 | 102 | return result.warn( 103 | "@import must precede all other statements (besides @charset or empty @layer)", 104 | { node: atRule }, 105 | ) 106 | } while (prev) 107 | } 108 | 109 | if (atRule.nodes) { 110 | return result.warn( 111 | "It looks like you didn't end your @import statement correctly. " + 112 | "Child nodes are attached to it.", 113 | { node: atRule }, 114 | ) 115 | } 116 | 117 | const params = valueParser(atRule.params).nodes 118 | const stmt = { 119 | type: "import", 120 | uri: "", 121 | fullUri: "", 122 | node: atRule, 123 | conditions: [...conditions], 124 | from, 125 | } 126 | 127 | let layer 128 | let media 129 | let supports 130 | 131 | for (let i = 0; i < params.length; i++) { 132 | const node = params[i] 133 | 134 | if (node.type === "space" || node.type === "comment") continue 135 | 136 | if (node.type === "string") { 137 | if (stmt.uri) { 138 | return result.warn(`Multiple url's in '${atRule.toString()}'`, { 139 | node: atRule, 140 | }) 141 | } 142 | 143 | if (!node.value) { 144 | return result.warn(`Unable to find uri in '${atRule.toString()}'`, { 145 | node: atRule, 146 | }) 147 | } 148 | 149 | stmt.uri = node.value 150 | stmt.fullUri = stringify(node) 151 | continue 152 | } 153 | 154 | if (node.type === "function" && /^url$/i.test(node.value)) { 155 | if (stmt.uri) { 156 | return result.warn(`Multiple url's in '${atRule.toString()}'`, { 157 | node: atRule, 158 | }) 159 | } 160 | 161 | if (!node.nodes?.[0]?.value) { 162 | return result.warn(`Unable to find uri in '${atRule.toString()}'`, { 163 | node: atRule, 164 | }) 165 | } 166 | 167 | stmt.uri = node.nodes[0].value 168 | stmt.fullUri = stringify(node) 169 | continue 170 | } 171 | 172 | if (!stmt.uri) { 173 | return result.warn(`Unable to find uri in '${atRule.toString()}'`, { 174 | node: atRule, 175 | }) 176 | } 177 | 178 | if ( 179 | (node.type === "word" || node.type === "function") && 180 | /^layer$/i.test(node.value) 181 | ) { 182 | if (typeof layer !== "undefined") { 183 | return result.warn(`Multiple layers in '${atRule.toString()}'`, { 184 | node: atRule, 185 | }) 186 | } 187 | 188 | if (typeof supports !== "undefined") { 189 | return result.warn( 190 | `layers must be defined before support conditions in '${atRule.toString()}'`, 191 | { 192 | node: atRule, 193 | }, 194 | ) 195 | } 196 | 197 | if (node.nodes) { 198 | layer = stringify(node.nodes) 199 | } else { 200 | layer = "" 201 | } 202 | 203 | continue 204 | } 205 | 206 | if (node.type === "function" && /^supports$/i.test(node.value)) { 207 | if (typeof supports !== "undefined") { 208 | return result.warn( 209 | `Multiple support conditions in '${atRule.toString()}'`, 210 | { 211 | node: atRule, 212 | }, 213 | ) 214 | } 215 | 216 | supports = stringify(node.nodes) 217 | 218 | continue 219 | } 220 | 221 | media = stringify(params.slice(i)) 222 | break 223 | } 224 | 225 | if (!stmt.uri) { 226 | return result.warn(`Unable to find uri in '${atRule.toString()}'`, { 227 | node: atRule, 228 | }) 229 | } 230 | 231 | if ( 232 | typeof media !== "undefined" || 233 | typeof layer !== "undefined" || 234 | typeof supports !== "undefined" 235 | ) { 236 | stmt.conditions.push({ 237 | layer, 238 | media, 239 | supports, 240 | }) 241 | } 242 | 243 | return stmt 244 | } 245 | 246 | function parseLayer(result, atRule, conditions, from) { 247 | return { 248 | type: "layer", 249 | node: atRule, 250 | conditions: [...conditions], 251 | from, 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /lib/parse-styles.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const path = require("path") 4 | 5 | const dataURL = require("./data-url") 6 | const parseStatements = require("./parse-statements") 7 | const processContent = require("./process-content") 8 | const resolveId = require("./resolve-id") 9 | const formatImportPrelude = require("./format-import-prelude") 10 | 11 | async function parseStyles( 12 | result, 13 | styles, 14 | options, 15 | state, 16 | conditions, 17 | from, 18 | postcss, 19 | ) { 20 | const statements = parseStatements(result, styles, conditions, from) 21 | 22 | for (const stmt of statements) { 23 | if (stmt.type !== "import" || !isProcessableURL(stmt.uri)) { 24 | continue 25 | } 26 | 27 | if (options.filter && !options.filter(stmt.uri)) { 28 | // rejected by filter 29 | continue 30 | } 31 | 32 | await resolveImportId(result, stmt, options, state, postcss) 33 | } 34 | 35 | let charset 36 | const beforeBundle = [] 37 | const bundle = [] 38 | 39 | function handleCharset(stmt) { 40 | if (!charset) charset = stmt 41 | // charsets aren't case-sensitive, so convert to lower case to compare 42 | else if ( 43 | stmt.node.params.toLowerCase() !== charset.node.params.toLowerCase() 44 | ) { 45 | throw stmt.node.error( 46 | `Incompatible @charset statements: 47 | ${stmt.node.params} specified in ${stmt.node.source.input.file} 48 | ${charset.node.params} specified in ${charset.node.source.input.file}`, 49 | ) 50 | } 51 | } 52 | 53 | // squash statements and their children 54 | statements.forEach(stmt => { 55 | if (stmt.type === "charset") handleCharset(stmt) 56 | else if (stmt.type === "import") { 57 | if (stmt.children) { 58 | stmt.children.forEach((child, index) => { 59 | if (child.type === "import") beforeBundle.push(child) 60 | else if (child.type === "layer") beforeBundle.push(child) 61 | else if (child.type === "charset") handleCharset(child) 62 | else bundle.push(child) 63 | // For better output 64 | if (index === 0) child.parent = stmt 65 | }) 66 | } else beforeBundle.push(stmt) 67 | } else if (stmt.type === "layer") { 68 | beforeBundle.push(stmt) 69 | } else if (stmt.type === "nodes") { 70 | bundle.push(stmt) 71 | } 72 | }) 73 | 74 | return charset 75 | ? [charset, ...beforeBundle.concat(bundle)] 76 | : beforeBundle.concat(bundle) 77 | } 78 | 79 | async function resolveImportId(result, stmt, options, state, postcss) { 80 | if (dataURL.isValid(stmt.uri)) { 81 | // eslint-disable-next-line require-atomic-updates 82 | stmt.children = await loadImportContent( 83 | result, 84 | stmt, 85 | stmt.uri, 86 | options, 87 | state, 88 | postcss, 89 | ) 90 | 91 | return 92 | } else if (dataURL.isValid(stmt.from.slice(-1))) { 93 | // Data urls can't be used as a base url to resolve imports. 94 | throw stmt.node.error( 95 | `Unable to import '${stmt.uri}' from a stylesheet that is embedded in a data url`, 96 | ) 97 | } 98 | 99 | const atRule = stmt.node 100 | let sourceFile 101 | if (atRule.source?.input?.file) { 102 | sourceFile = atRule.source.input.file 103 | } 104 | const base = sourceFile 105 | ? path.dirname(atRule.source.input.file) 106 | : options.root 107 | 108 | const paths = [await options.resolve(stmt.uri, base, options, atRule)].flat() 109 | 110 | // Ensure that each path is absolute: 111 | const resolved = await Promise.all( 112 | paths.map(file => { 113 | return !path.isAbsolute(file) 114 | ? resolveId(file, base, options, atRule) 115 | : file 116 | }), 117 | ) 118 | 119 | // Add dependency messages: 120 | resolved.forEach(file => { 121 | result.messages.push({ 122 | type: "dependency", 123 | plugin: "postcss-import", 124 | file, 125 | parent: sourceFile, 126 | }) 127 | }) 128 | 129 | const importedContent = await Promise.all( 130 | resolved.map(file => { 131 | return loadImportContent(result, stmt, file, options, state, postcss) 132 | }), 133 | ) 134 | 135 | // Merge loaded statements 136 | // eslint-disable-next-line require-atomic-updates 137 | stmt.children = importedContent.flat().filter(x => !!x) 138 | } 139 | 140 | async function loadImportContent( 141 | result, 142 | stmt, 143 | filename, 144 | options, 145 | state, 146 | postcss, 147 | ) { 148 | const atRule = stmt.node 149 | const { conditions, from } = stmt 150 | const stmtDuplicateCheckKey = conditions 151 | .map(condition => 152 | formatImportPrelude(condition.layer, condition.media, condition.supports), 153 | ) 154 | .join(":") 155 | 156 | if (options.skipDuplicates) { 157 | // skip files already imported at the same scope 158 | if (state.importedFiles[filename]?.[stmtDuplicateCheckKey]) { 159 | return 160 | } 161 | 162 | // save imported files to skip them next time 163 | if (!state.importedFiles[filename]) { 164 | state.importedFiles[filename] = {} 165 | } 166 | state.importedFiles[filename][stmtDuplicateCheckKey] = true 167 | } 168 | 169 | if (from.includes(filename)) { 170 | return 171 | } 172 | 173 | const content = await options.load(filename, options) 174 | 175 | if (content.trim() === "" && options.warnOnEmpty) { 176 | result.warn(`${filename} is empty`, { node: atRule }) 177 | return 178 | } 179 | 180 | // skip previous imported files not containing @import rules 181 | if ( 182 | options.skipDuplicates && 183 | state.hashFiles[content]?.[stmtDuplicateCheckKey] 184 | ) { 185 | return 186 | } 187 | 188 | const importedResult = await processContent( 189 | result, 190 | content, 191 | filename, 192 | options, 193 | postcss, 194 | ) 195 | 196 | const styles = importedResult.root 197 | result.messages = result.messages.concat(importedResult.messages) 198 | 199 | if (options.skipDuplicates) { 200 | const hasImport = styles.some(child => { 201 | return child.type === "atrule" && child.name === "import" 202 | }) 203 | if (!hasImport) { 204 | // save hash files to skip them next time 205 | if (!state.hashFiles[content]) { 206 | state.hashFiles[content] = {} 207 | } 208 | 209 | state.hashFiles[content][stmtDuplicateCheckKey] = true 210 | } 211 | } 212 | 213 | // recursion: import @import from imported file 214 | return parseStyles( 215 | result, 216 | styles, 217 | options, 218 | state, 219 | conditions, 220 | [...from, filename], 221 | postcss, 222 | ) 223 | } 224 | 225 | function isProcessableURL(uri) { 226 | // skip protocol base uri (protocol://url) or protocol-relative 227 | if (/^(?:[a-z]+:)?\/\//i.test(uri)) { 228 | return false 229 | } 230 | 231 | // check for fragment or query 232 | try { 233 | // needs a base to parse properly 234 | const url = new URL(uri, "https://example.com") 235 | if (url.search) { 236 | return false 237 | } 238 | } catch {} // Ignore 239 | 240 | return true 241 | } 242 | 243 | module.exports = parseStyles 244 | -------------------------------------------------------------------------------- /lib/process-content.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | // builtin tooling 4 | const path = require("path") 5 | 6 | // placeholder tooling 7 | let sugarss 8 | 9 | module.exports = function processContent( 10 | result, 11 | content, 12 | filename, 13 | options, 14 | postcss, 15 | ) { 16 | const { plugins } = options 17 | const ext = path.extname(filename) 18 | 19 | const parserList = [] 20 | 21 | // SugarSS support: 22 | if (ext === ".sss") { 23 | if (!sugarss) { 24 | /* c8 ignore next 3 */ 25 | try { 26 | sugarss = require("sugarss") 27 | } catch {} // Ignore 28 | } 29 | if (sugarss) 30 | return runPostcss(postcss, content, filename, plugins, [sugarss]) 31 | } 32 | 33 | // Syntax support: 34 | if (result.opts.syntax?.parse) { 35 | parserList.push(result.opts.syntax.parse) 36 | } 37 | 38 | // Parser support: 39 | if (result.opts.parser) parserList.push(result.opts.parser) 40 | // Try the default as a last resort: 41 | parserList.push(null) 42 | 43 | return runPostcss(postcss, content, filename, plugins, parserList) 44 | } 45 | 46 | function runPostcss(postcss, content, filename, plugins, parsers, index) { 47 | if (!index) index = 0 48 | return postcss(plugins) 49 | .process(content, { 50 | from: filename, 51 | parser: parsers[index], 52 | }) 53 | .catch(err => { 54 | // If there's an error, try the next parser 55 | index++ 56 | // If there are no parsers left, throw it 57 | if (index === parsers.length) throw err 58 | return runPostcss(postcss, content, filename, plugins, parsers, index) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /lib/resolve-id.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | // external tooling 4 | const resolve = require("resolve") 5 | 6 | const moduleDirectories = ["web_modules", "node_modules"] 7 | 8 | function resolveModule(id, opts) { 9 | return new Promise((res, rej) => { 10 | resolve(id, opts, (err, path) => (err ? rej(err) : res(path))) 11 | }) 12 | } 13 | 14 | module.exports = function resolveId(id, base, options, node) { 15 | const paths = options.path 16 | 17 | const resolveOpts = { 18 | basedir: base, 19 | moduleDirectory: moduleDirectories.concat(options.addModulesDirectories), 20 | paths, 21 | extensions: [".css"], 22 | packageFilter: function processPackage(pkg) { 23 | if (pkg.style) pkg.main = pkg.style 24 | else if (!pkg.main || !/\.css$/.test(pkg.main)) pkg.main = "index.css" 25 | return pkg 26 | }, 27 | preserveSymlinks: false, 28 | } 29 | 30 | return resolveModule(`./${id}`, resolveOpts) 31 | .catch(() => resolveModule(id, resolveOpts)) 32 | .catch(() => { 33 | if (paths.indexOf(base) === -1) paths.unshift(base) 34 | 35 | throw node.error( 36 | `Failed to find '${id}' 37 | in [ 38 | ${paths.join(",\n ")} 39 | ]`, 40 | ) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-import", 3 | "version": "16.1.0", 4 | "description": "PostCSS plugin to import CSS files", 5 | "keywords": [ 6 | "css", 7 | "postcss", 8 | "postcss-plugin", 9 | "import", 10 | "node modules", 11 | "npm" 12 | ], 13 | "author": "Maxime Thirouin", 14 | "license": "MIT", 15 | "repository": "https://github.com/postcss/postcss-import.git", 16 | "files": [ 17 | "index.js", 18 | "lib" 19 | ], 20 | "engines": { 21 | "node": ">=18.0.0" 22 | }, 23 | "dependencies": { 24 | "postcss-value-parser": "^4.0.0", 25 | "read-cache": "^1.0.0", 26 | "resolve": "^1.1.7" 27 | }, 28 | "devDependencies": { 29 | "ava": "^6.0.0", 30 | "c8": "^10.0.0", 31 | "eslint": "^8.27.0", 32 | "eslint-config-problems": "^8.0.0", 33 | "eslint-plugin-prettier": "^5.0.0", 34 | "postcss": "^8.0.0", 35 | "postcss-scss": "^4.0.0", 36 | "prettier": "~3.5.0", 37 | "sugarss": "^5.0.0" 38 | }, 39 | "peerDependencies": { 40 | "postcss": "^8.0.0" 41 | }, 42 | "scripts": { 43 | "ci": "eslint . && ava", 44 | "lint": "eslint . --fix", 45 | "pretest": "npm run lint", 46 | "test": "c8 ava" 47 | }, 48 | "eslintConfig": { 49 | "extends": "eslint-config-problems", 50 | "env": { 51 | "node": true 52 | }, 53 | "plugins": [ 54 | "prettier" 55 | ], 56 | "rules": { 57 | "prettier/prettier": [ 58 | "error", 59 | { 60 | "semi": false, 61 | "arrowParens": "avoid" 62 | } 63 | ] 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:recommended", 4 | ":preserveSemverRanges", 5 | ":label(deps)" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/custom-load.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | 5 | // internal tooling 6 | const checkFixture = require("./helpers/check-fixture") 7 | 8 | test.serial("should accept content", checkFixture, "custom-load", { 9 | load: () => "custom-content {}", 10 | }) 11 | 12 | test.serial("should accept promised content", checkFixture, "custom-load", { 13 | load: () => Promise.resolve("custom-content {}"), 14 | }) 15 | -------------------------------------------------------------------------------- /test/custom-resolve.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // builtin tooling 3 | const path = require("path") 4 | 5 | // external tooling 6 | const test = require("ava") 7 | const postcss = require("postcss") 8 | 9 | // plugin 10 | const atImport = require("..") 11 | 12 | // internal tooling 13 | const checkFixture = require("./helpers/check-fixture") 14 | 15 | test.serial("should accept file", checkFixture, "custom-resolve-file", { 16 | resolve: () => path.resolve("test/fixtures/imports/custom-resolve-1.css"), 17 | }) 18 | 19 | test.serial( 20 | "should accept promised file", 21 | checkFixture, 22 | "custom-resolve-file", 23 | { 24 | resolve: () => { 25 | return Promise.resolve( 26 | path.resolve("test/fixtures/imports/custom-resolve-1.css"), 27 | ) 28 | }, 29 | }, 30 | ) 31 | 32 | test.serial( 33 | "should accept array of files", 34 | checkFixture, 35 | "custom-resolve-array", 36 | { 37 | resolve: () => { 38 | return [ 39 | path.resolve("test/fixtures/imports/custom-resolve-1.css"), 40 | path.resolve("test/fixtures/imports/custom-resolve-2.css"), 41 | path.resolve("test/fixtures/imports/custom-resolve-1.css"), 42 | ] 43 | }, 44 | }, 45 | ) 46 | 47 | test.serial( 48 | "should accept promised array of files", 49 | checkFixture, 50 | "custom-resolve-array", 51 | { 52 | resolve: () => { 53 | return Promise.resolve([ 54 | path.resolve("test/fixtures/imports/custom-resolve-1.css"), 55 | path.resolve("test/fixtures/imports/custom-resolve-2.css"), 56 | path.resolve("test/fixtures/imports/custom-resolve-1.css"), 57 | ]) 58 | }, 59 | }, 60 | ) 61 | 62 | test("should apply default resolver when custom doesn't return an absolute path", t => { 63 | return postcss() 64 | .use( 65 | atImport({ 66 | resolve: path => path.replace("foo", "imports/bar"), 67 | load: p => { 68 | t.is(p, path.resolve("test/fixtures/imports", "bar.css")) 69 | return "/* comment */" 70 | }, 71 | }), 72 | ) 73 | .process(`@import "foo.css";`, { 74 | from: "test/fixtures/custom-resolve-file", 75 | }) 76 | .then(result => t.is(result.css, "/* comment */")) 77 | }) 78 | -------------------------------------------------------------------------------- /test/custom-syntax-parser.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | const scss = require("postcss-scss") 5 | const sugarss = require("sugarss") 6 | 7 | // internal tooling 8 | const checkFixture = require("./helpers/check-fixture") 9 | 10 | test("should process custom syntax", checkFixture, "scss-syntax", null, { 11 | syntax: scss, 12 | }) 13 | 14 | test( 15 | "should process custom syntax by parser", 16 | checkFixture, 17 | "scss-parser", 18 | null, 19 | { parser: scss }, 20 | ) 21 | 22 | test(".css importing .sss should work", checkFixture, "import-sss") 23 | 24 | test( 25 | ".sss importing .sss should work", 26 | checkFixture, 27 | { name: "sugar", ext: ".sss" }, 28 | null, 29 | { parser: sugarss }, 30 | ) 31 | 32 | test( 33 | ".sss importing .css should work", 34 | checkFixture, 35 | { name: "sugar-import-css", ext: ".sss" }, 36 | null, 37 | { parser: sugarss }, 38 | ) 39 | 40 | test( 41 | ".css importing .sss importing .css should work", 42 | checkFixture, 43 | "import-sss-css", 44 | ) 45 | 46 | test( 47 | ".sss importing .css importing .sss should work", 48 | checkFixture, 49 | { name: "import-css-sss", ext: ".sss" }, 50 | null, 51 | { parser: sugarss }, 52 | ) 53 | -------------------------------------------------------------------------------- /test/data-url.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | const postcss = require("postcss") 5 | 6 | // plugin 7 | const atImport = require("..") 8 | 9 | // internal tooling 10 | const checkFixture = require("./helpers/check-fixture") 11 | 12 | test("should inline data urls", checkFixture, "data-url") 13 | 14 | test("should error on relative urls from stylesheets in data urls", t => { 15 | return postcss() 16 | .use(atImport()) 17 | .process( 18 | "@import url(data:text/css;base64,QGltcG9ydCB1cmwoZm9vLmNzcyk7CgpwIHsKICBjb2xvcjogYmx1ZTsKfQo=);", 19 | { from: undefined }, 20 | ) 21 | .catch(error => 22 | t.regex( 23 | error.message, 24 | /Unable to import '(?:.*?)' from a stylesheet that is embedded in a data url/, 25 | ), 26 | ) 27 | }) 28 | -------------------------------------------------------------------------------- /test/filter.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | 5 | // internal tooling 6 | const checkFixture = require("./helpers/check-fixture") 7 | 8 | test("should filter all imported stylesheets", checkFixture, "filter-all", { 9 | filter: () => false, 10 | }) 11 | 12 | test("should filter some stylesheets", checkFixture, "filter-some", { 13 | filter: url => url !== "foobar.css", 14 | }) 15 | 16 | test("shouldn't accept ignored stylesheets", checkFixture, "filter-ignore", { 17 | filter: () => true, 18 | }) 19 | -------------------------------------------------------------------------------- /test/fixtures/charset-error.css: -------------------------------------------------------------------------------- 1 | @charset "foobar"; 2 | @import "imports/charset.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/charset-import.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @import "test/fixtures/imports/foo.css"; 3 | @import "test/fixtures/imports/charset.css"; 4 | bar{} 5 | -------------------------------------------------------------------------------- /test/fixtures/charset-import.expected.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | foo{} 3 | bar{} 4 | -------------------------------------------------------------------------------- /test/fixtures/custom-load.css: -------------------------------------------------------------------------------- 1 | @import "foo" 2 | -------------------------------------------------------------------------------- /test/fixtures/custom-load.expected.css: -------------------------------------------------------------------------------- 1 | custom-content {} 2 | -------------------------------------------------------------------------------- /test/fixtures/custom-resolve-array.css: -------------------------------------------------------------------------------- 1 | @import "any-path"; 2 | -------------------------------------------------------------------------------- /test/fixtures/custom-resolve-array.expected.css: -------------------------------------------------------------------------------- 1 | custom-resolve-1 {} 2 | custom-resolve-2 {} 3 | -------------------------------------------------------------------------------- /test/fixtures/custom-resolve-file.css: -------------------------------------------------------------------------------- 1 | @import "any-path"; 2 | -------------------------------------------------------------------------------- /test/fixtures/custom-resolve-file.expected.css: -------------------------------------------------------------------------------- 1 | custom-resolve-1 {} 2 | -------------------------------------------------------------------------------- /test/fixtures/cyclical-skip-duplicates.css: -------------------------------------------------------------------------------- 1 | @import "cyclical-a.css"; 2 | @import "cyclical-b.css"; 3 | 4 | @import "cyclical-screen.css" screen; 5 | -------------------------------------------------------------------------------- /test/fixtures/cyclical-skip-duplicates.expected.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .b { 4 | color: red; 5 | } 6 | 7 | .a { 8 | color: blue; 9 | } 10 | 11 | @media screen { 12 | 13 | @media all { 14 | 15 | .a { 16 | color: aquamarine; 17 | } 18 | } 19 | } 20 | 21 | @media screen { 22 | 23 | .a { 24 | color: pink; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/cyclical.css: -------------------------------------------------------------------------------- 1 | @import "cyclical-a.css"; 2 | @import "cyclical-b.css"; 3 | 4 | @import "cyclical-screen.css" screen; 5 | -------------------------------------------------------------------------------- /test/fixtures/cyclical.expected.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .b { 4 | color: red; 5 | } 6 | 7 | .a { 8 | color: blue; 9 | } 10 | 11 | .a { 12 | color: blue; 13 | } 14 | 15 | .b { 16 | color: red; 17 | } 18 | 19 | @media screen { 20 | 21 | @media all { 22 | 23 | .a { 24 | color: aquamarine; 25 | } 26 | } 27 | } 28 | 29 | @media screen { 30 | 31 | .a { 32 | color: pink; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/fixtures/data-url.css: -------------------------------------------------------------------------------- 1 | @import url(data:text/css;base64,QGltcG9ydCB1cmwoZGF0YTp0ZXh0L2NzcztiYXNlNjQsY0NCN0lHTnZiRzl5T2lCbmNtVmxianNnZlE9PSk7CgpwIHsgY29sb3I6IGJsdWU7IH0K); 2 | @import url("DATA:TEXT/CSS;BASE64,QGltcG9ydCB1cmwoZGF0YTp0ZXh0L2NzcztiYXNlNjQsY0NCN0lHTnZiRzl5T2lCbmNtVmxianNnZlE9PSk7CgpwIHsgY29sb3I6IGJsdWU7IH0K") layer(foo) (min-width: 320px); 3 | 4 | /* Mixed imports: */ 5 | @import url(data-url.css); 6 | 7 | /* url encoded: */ 8 | @import url(data:text/css;plain,bar%20%7B%20color%3A%20green%20%7D); 9 | @import url(data:text/css,bar%20%7B%20color%3A%20pink%20%7D); 10 | -------------------------------------------------------------------------------- /test/fixtures/data-url.expected.css: -------------------------------------------------------------------------------- 1 | p { color: green; } 2 | 3 | p { color: blue; } 4 | 5 | @media (min-width: 320px) { 6 | 7 | @layer foo { 8 | p { color: green; } } } 9 | 10 | @media (min-width: 320px) { 11 | 12 | @layer foo { 13 | 14 | p { color: blue; } } } 15 | 16 | /* Mixed imports: */ 17 | 18 | p { color: pink; } 19 | 20 | /* url encoded: */ 21 | 22 | bar { color: green } 23 | 24 | bar { color: pink } 25 | -------------------------------------------------------------------------------- /test/fixtures/duplicates.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import "foo.css"; 3 | @import "foo-duplicate.css"; 4 | 5 | @import "foo.css" screen; 6 | @import "foo-duplicate2" screen; 7 | 8 | @import "proxy-file/index.css"; 9 | @import "proxy-file/sub-directory/index.css"; 10 | 11 | content{} 12 | -------------------------------------------------------------------------------- /test/fixtures/duplicates.expected.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | foo{} 3 | foo{} 4 | @media screen{ 5 | foo{} 6 | } 7 | @media screen{ 8 | foo{} 9 | } 10 | proxy {} 11 | import {} 12 | content{} 13 | -------------------------------------------------------------------------------- /test/fixtures/empty-and-useless.css: -------------------------------------------------------------------------------- 1 | 2 | @import "empty.css"; 3 | 4 | @import "useless.css"; 5 | -------------------------------------------------------------------------------- /test/fixtures/empty-and-useless.expected.css: -------------------------------------------------------------------------------- 1 | 2 | /* useless */ 3 | -------------------------------------------------------------------------------- /test/fixtures/filter-all.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import "foo.css" screen; 3 | @import 'bar.css'; 4 | @import 'bar.css' screen; 5 | @import url(baz.css); 6 | @import url(baz.css) screen; 7 | @import url("foobar.css"); 8 | @import url("foobar.css") screen and (min-width: 25em); 9 | @import url('foobarbaz.css'); 10 | @import url('foobarbaz.css') print, screen and (min-width: 25em); 11 | 12 | content{} 13 | -------------------------------------------------------------------------------- /test/fixtures/filter-all.expected.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import "foo.css" screen; 3 | @import 'bar.css'; 4 | @import 'bar.css' screen; 5 | @import url(baz.css); 6 | @import url(baz.css) screen; 7 | @import url("foobar.css"); 8 | @import url("foobar.css") screen and (min-width: 25em); 9 | @import url('foobarbaz.css'); 10 | @import url('foobarbaz.css') print, screen and (min-width: 25em); 11 | content{} 12 | -------------------------------------------------------------------------------- /test/fixtures/filter-ignore.css: -------------------------------------------------------------------------------- 1 | @import "ignore.css" (min-width: 25em); 2 | @import "http://css"; 3 | @import "https://css"; 4 | @import 'http://css'; 5 | @import 'https://css'; 6 | @import url(http://css); 7 | @import url(https://css); 8 | @import url("http://css"); 9 | @import url("https://css"); 10 | @import url('http://css'); 11 | @import url('https://css'); 12 | @import url("//css"); 13 | @import url('//css'); 14 | @import url(//css); 15 | @import url('foo.css?query=string'); 16 | content{} 17 | -------------------------------------------------------------------------------- /test/fixtures/filter-ignore.expected.css: -------------------------------------------------------------------------------- 1 | @import "http://css" (min-width: 25em); 2 | @import 'data:text/css;base64,QGltcG9ydCAiaHR0cDovL2Nzcy1zY3JlZW4iIChtaW4td2lkdGg6IDI1ZW0p' screen; 3 | @import "http://css"; 4 | @import "https://css"; 5 | @import 'http://css'; 6 | @import 'https://css'; 7 | @import url(http://css); 8 | @import url(https://css); 9 | @import url("http://css"); 10 | @import url("https://css"); 11 | @import url('http://css'); 12 | @import url('https://css'); 13 | @import url("//css"); 14 | @import url('//css'); 15 | @import url(//css); 16 | @import url('foo.css?query=string'); 17 | @media (min-width: 25em){ 18 | ignore{} 19 | } 20 | content{} 21 | -------------------------------------------------------------------------------- /test/fixtures/filter-some.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import "foo.css" screen; 3 | @import 'bar.css'; 4 | @import 'bar.css' screen; 5 | @import url(baz.css); 6 | @import url(baz.css) screen; 7 | @import url("foobar.css"); 8 | @import url("foobar.css") screen and (min-width: 25em); 9 | @import url('foobarbaz.css'); 10 | @import url('foobarbaz.css') print, screen and (min-width: 25em); 11 | 12 | content{} 13 | -------------------------------------------------------------------------------- /test/fixtures/filter-some.expected.css: -------------------------------------------------------------------------------- 1 | 2 | @import url("foobar.css"); 3 | @import url("foobar.css") screen and (min-width: 25em); 4 | foo{} 5 | @media screen{ 6 | foo{} 7 | } 8 | bar{} 9 | @media screen{ 10 | bar{} 11 | } 12 | baz{} 13 | @media screen{ 14 | baz{} 15 | } 16 | foobarbaz{} 17 | @media print, screen and (min-width: 25em){ 18 | foobarbaz{} 19 | } 20 | content{} 21 | -------------------------------------------------------------------------------- /test/fixtures/ignore.css: -------------------------------------------------------------------------------- 1 | @import "ignore.css" (min-width: 25em); 2 | @import "http://css"; 3 | @import "https://css"; 4 | @import 'http://css'; 5 | @import 'https://css'; 6 | @import url(http://css); 7 | @import url(https://css); 8 | @import url("http://css"); 9 | @import url("https://css"); 10 | @import url('http://css'); 11 | @import url('https://css'); 12 | @import url("//css"); 13 | @import url('//css'); 14 | @import url(//css); 15 | @import "http://css" layer; 16 | @import "http://css" layer(bar); 17 | @import "http://css" layer screen and (min-width: 25em), print; 18 | @import "http://css" layer(bar) screen and (min-width: 25em), print; 19 | 20 | content{} 21 | -------------------------------------------------------------------------------- /test/fixtures/ignore.expected.css: -------------------------------------------------------------------------------- 1 | @import "http://css" (min-width: 25em); 2 | @import 'data:text/css;base64,QGltcG9ydCAiaHR0cDovL2Nzcy1zY3JlZW4iIChtaW4td2lkdGg6IDI1ZW0p' screen; 3 | @import "http://css"; 4 | @import "https://css"; 5 | @import 'http://css'; 6 | @import 'https://css'; 7 | @import url(http://css); 8 | @import url(https://css); 9 | @import url("http://css"); 10 | @import url("https://css"); 11 | @import url('http://css'); 12 | @import url('https://css'); 13 | @import url("//css"); 14 | @import url('//css'); 15 | @import url(//css); 16 | @import "http://css" layer; 17 | @import "http://css" layer(bar); 18 | @import "http://css" layer screen and (min-width: 25em), print; 19 | @import "http://css" layer(bar) screen and (min-width: 25em), print; 20 | @media (min-width: 25em){ 21 | ignore{} 22 | } 23 | content{} 24 | -------------------------------------------------------------------------------- /test/fixtures/import-css-sss.expected.css: -------------------------------------------------------------------------------- 1 | .sugarbar{ 2 | color: blue 3 | } 4 | 5 | import.sugarbar{} 6 | 7 | .sugar{ 8 | color: white 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/import-css-sss.sss: -------------------------------------------------------------------------------- 1 | @import "import-sugarbar.css" 2 | 3 | .sugar 4 | color: white 5 | -------------------------------------------------------------------------------- /test/fixtures/import-sss-css.css: -------------------------------------------------------------------------------- 1 | @import "foo-recursive.sss"; 2 | -------------------------------------------------------------------------------- /test/fixtures/import-sss-css.expected.css: -------------------------------------------------------------------------------- 1 | bar{} 2 | 3 | foo.recursive{ 4 | color: red 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/import-sss.css: -------------------------------------------------------------------------------- 1 | @import "sugarbar.sss"; 2 | -------------------------------------------------------------------------------- /test/fixtures/import-sss.expected.css: -------------------------------------------------------------------------------- 1 | .sugarbar { 2 | color: blue 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/bar-decl.css: -------------------------------------------------------------------------------- 1 | body { 2 | bar: bar; 3 | qux: qux; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/imports/bar.css: -------------------------------------------------------------------------------- 1 | bar{} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/bar/baz.css: -------------------------------------------------------------------------------- 1 | bar {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/bar/index.css: -------------------------------------------------------------------------------- 1 | @import "baz.css" 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/barbaz.css: -------------------------------------------------------------------------------- 1 | barbaz{} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/baz.css: -------------------------------------------------------------------------------- 1 | baz{} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/charset-content.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | .charset-content {} 3 | -------------------------------------------------------------------------------- /test/fixtures/imports/charset.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/custom-resolve-1.css: -------------------------------------------------------------------------------- 1 | custom-resolve-1 {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/custom-resolve-2.css: -------------------------------------------------------------------------------- 1 | custom-resolve-2 {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/cyclical-a.css: -------------------------------------------------------------------------------- 1 | @import url(cyclical-b.css); 2 | 3 | .a { 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/imports/cyclical-all.css: -------------------------------------------------------------------------------- 1 | @import url(cyclical-screen.css) screen; 2 | 3 | .a { 4 | color: aquamarine; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/imports/cyclical-b.css: -------------------------------------------------------------------------------- 1 | @import url(cyclical-a.css); 2 | 3 | .b { 4 | color: red; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/imports/cyclical-screen.css: -------------------------------------------------------------------------------- 1 | @import url(cyclical-all.css) all; 2 | 3 | .a { 4 | color: pink; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/imports/data-url.css: -------------------------------------------------------------------------------- 1 | @import url("DATA:text/CSS;BASE64,cCB7IGNvbG9yOiBwaW5rOyB9"); 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/empty.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/postcss/postcss-import/92276420402a400566f30d7b494f8b51ef76b0f1/test/fixtures/imports/empty.css -------------------------------------------------------------------------------- /test/fixtures/imports/foo-decl.css: -------------------------------------------------------------------------------- 1 | body { 2 | foo: foo; 3 | baz: baz; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo-duplicate.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo-duplicate2.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo-first.css: -------------------------------------------------------------------------------- 1 | @import "bar.css"; 2 | 3 | foo.first{ 4 | color: red; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo-layered.css: -------------------------------------------------------------------------------- 1 | @layer foo { 2 | foo {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo-recursive.css: -------------------------------------------------------------------------------- 1 | @import "bar.css"; 2 | 3 | foo.recursive{} 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo-recursive.sss: -------------------------------------------------------------------------------- 1 | @import "bar.css" 2 | 3 | foo.recursive 4 | color: red 5 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo-second.css: -------------------------------------------------------------------------------- 1 | @import "bar.css"; 2 | 3 | foo.second{ 4 | color: blue; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo/baz.css: -------------------------------------------------------------------------------- 1 | foo {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/foo/index.css: -------------------------------------------------------------------------------- 1 | @import "baz.css" 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/foobar.css: -------------------------------------------------------------------------------- 1 | foobar{} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/foobarbaz.css: -------------------------------------------------------------------------------- 1 | foobarbaz{} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/ignore.css: -------------------------------------------------------------------------------- 1 | @import "http://css"; 2 | @import "http://css-screen" screen; 3 | ignore{} 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/import-missing.css: -------------------------------------------------------------------------------- 1 | 2 | @import "missing-file.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/imports/import-sugarbar.css: -------------------------------------------------------------------------------- 1 | @import "sugarbar.sss"; 2 | 3 | import.sugarbar{} 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/inline-comment.scss: -------------------------------------------------------------------------------- 1 | // inline comment 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/layer-anonymous.css: -------------------------------------------------------------------------------- 1 | @import url("layer-level-2-anonymous.css") layer; 2 | 3 | body { 4 | order: 1; 5 | } 6 | 7 | @media (min-width: 50rem) { 8 | body { 9 | order: 2; 10 | } 11 | } 12 | 13 | @keyframes RED_TO_BLUE { 14 | 0% { 15 | background-color: red; 16 | } 17 | 100% { 18 | background-color: blue; 19 | } 20 | } 21 | 22 | @supports (display: grid) { 23 | body { 24 | display: grid; 25 | order: 3; 26 | } 27 | } 28 | 29 | @layer A { 30 | body { 31 | order: 4; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/fixtures/imports/layer-followed-by-ignore.css: -------------------------------------------------------------------------------- 1 | /* a comment */ 2 | 3 | @layer layer-alpha; 4 | @import "http://css"; 5 | -------------------------------------------------------------------------------- /test/fixtures/imports/layer-level-2-anonymous.css: -------------------------------------------------------------------------------- 1 | @import "http://css" layer; 2 | @import "http://css-bar" layer(bar); 3 | @import url("layer-level-3.css") layer (min-width: 320px); 4 | 5 | @layer Y { 6 | body { 7 | color: purple; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/imports/layer-level-2.css: -------------------------------------------------------------------------------- 1 | @import url("layer-level-3.css") layer(level-3) (min-width: 320px); 2 | 3 | @layer Y { 4 | body { 5 | color: purple; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/imports/layer-level-3.css: -------------------------------------------------------------------------------- 1 | @layer Z { 2 | body { 3 | color: cyan; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/imports/layer-only.css: -------------------------------------------------------------------------------- 1 | @layer layer-beta; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/layer.css: -------------------------------------------------------------------------------- 1 | @import url("layer-level-2.css") layer(level-2); 2 | 3 | body { 4 | order: 1; 5 | } 6 | 7 | @media (min-width: 50rem) { 8 | body { 9 | order: 2; 10 | } 11 | } 12 | 13 | @keyframes RED_TO_BLUE { 14 | 0% { 15 | background-color: red; 16 | } 17 | 100% { 18 | background-color: blue; 19 | } 20 | } 21 | 22 | @supports (display: grid) { 23 | body { 24 | display: grid; 25 | order: 3; 26 | } 27 | } 28 | 29 | @layer A { 30 | body { 31 | order: 4; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/fixtures/imports/media-combine-nested.css: -------------------------------------------------------------------------------- 1 | @media one-2 {} 2 | 3 | @media or-left-2, or-right-2 {} 4 | 5 | @media and-left-2 and and-right-2 {} 6 | -------------------------------------------------------------------------------- /test/fixtures/imports/media-content-level-2.css: -------------------------------------------------------------------------------- 1 | @import "media-content-level-3"; 2 | 3 | level-2 {} 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/media-content-level-3.css: -------------------------------------------------------------------------------- 1 | level-3 {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/media-import-level-2.css: -------------------------------------------------------------------------------- 1 | @import "media-import-level-3" level-2; 2 | 3 | @import "//" level-2; 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/media-import-level-3.css: -------------------------------------------------------------------------------- 1 | @import "//" level-3; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/media-query-level-2.css: -------------------------------------------------------------------------------- 1 | @import "media-query-level-3" level-2; 2 | 3 | @media level-2 {} 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/media-query-level-3.css: -------------------------------------------------------------------------------- 1 | @media level-3 {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/media-query-screen.css: -------------------------------------------------------------------------------- 1 | @import "foo.css" screen; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/empty/index.css: -------------------------------------------------------------------------------- 1 | empty {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/empty/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/main-js/index.css: -------------------------------------------------------------------------------- 1 | main-js {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/main-js/main.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | module.exports = {} 3 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/main-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "main.js" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/main-style/main.css: -------------------------------------------------------------------------------- 1 | style-mained {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/main-style/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "main.css", 3 | "style": "style.css" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/main-style/style.css: -------------------------------------------------------------------------------- 1 | main-styled {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/main/main.css: -------------------------------------------------------------------------------- 1 | main {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "main.css" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/simple/index.css: -------------------------------------------------------------------------------- 1 | simple {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/style/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style": "style.css" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/style/style.css: -------------------------------------------------------------------------------- 1 | style {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/subpath/a.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | color: green; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/subpath/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style": "style.css", 3 | "imports": { 4 | "#a.css": "./a.css" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/imports/modules/subpath/style.css: -------------------------------------------------------------------------------- 1 | @import '#a.css'; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/proxy-file/index.css: -------------------------------------------------------------------------------- 1 | @import "./proxy-file.css"; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/proxy-file/proxy-file.css: -------------------------------------------------------------------------------- 1 | proxy {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/proxy-file/sub-directory/index.css: -------------------------------------------------------------------------------- 1 | @import "./proxy-file.css"; 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/proxy-file/sub-directory/proxy-file.css: -------------------------------------------------------------------------------- 1 | import {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/qux.css: -------------------------------------------------------------------------------- 1 | quux{ 2 | not: "relative/qux" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/sugarbar.sss: -------------------------------------------------------------------------------- 1 | .sugarbar 2 | color: blue 3 | -------------------------------------------------------------------------------- /test/fixtures/imports/supports-condition-level-2.css: -------------------------------------------------------------------------------- 1 | @import "supports-condition-level-3.css" layer supports(color: green) screen; 2 | 3 | @supports (level: 2) {} 4 | -------------------------------------------------------------------------------- /test/fixtures/imports/supports-condition-level-3.css: -------------------------------------------------------------------------------- 1 | @supports (level: 3) {} 2 | -------------------------------------------------------------------------------- /test/fixtures/imports/syntax-error.css: -------------------------------------------------------------------------------- 1 | body { 2 | bar: bar; 3 | qux: qux; 4 | 5 | a { 6 | foo: foo; 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/imports/useless.css: -------------------------------------------------------------------------------- 1 | 2 | /* useless */ 3 | -------------------------------------------------------------------------------- /test/fixtures/layer-duplicate-anonymous-imports-skip.css: -------------------------------------------------------------------------------- 1 | @import url("foo.css") layer; 2 | @import url("bar.css") layer; 3 | @import url("foo.css") layer; 4 | -------------------------------------------------------------------------------- /test/fixtures/layer-duplicate-anonymous-imports-skip.expected.css: -------------------------------------------------------------------------------- 1 | @layer{ 2 | foo{} 3 | } 4 | @layer{ 5 | bar{} 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/layer-duplicate-anonymous-imports.css: -------------------------------------------------------------------------------- 1 | @import url("foo.css") layer; 2 | @import url("bar.css") layer; 3 | @import url("foo.css") layer; 4 | -------------------------------------------------------------------------------- /test/fixtures/layer-duplicate-anonymous-imports.expected.css: -------------------------------------------------------------------------------- 1 | @layer{ 2 | foo{} 3 | } 4 | @layer{ 5 | bar{} 6 | } 7 | @layer{ 8 | foo{} 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/layer-followed-by-ignore-with-conditions.css: -------------------------------------------------------------------------------- 1 | @import "layer-only.css" print; 2 | @import "layer-followed-by-ignore.css" screen; 3 | -------------------------------------------------------------------------------- /test/fixtures/layer-followed-by-ignore-with-conditions.expected.css: -------------------------------------------------------------------------------- 1 | @import 'data:text/css;base64,QGxheWVyIGxheWVyLWJldGE=' print; 2 | @import 'data:text/css;base64,QGxheWVyIGxheWVyLWFscGhh' screen; 3 | @import "http://css" screen; 4 | @media screen{ 5 | /* a comment */ 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/layer-followed-by-ignore-without-conditions.css: -------------------------------------------------------------------------------- 1 | @import "http://css-a"; 2 | @import url("layer-only.css"); 3 | @import "http://css-b"; 4 | -------------------------------------------------------------------------------- /test/fixtures/layer-followed-by-ignore-without-conditions.expected.css: -------------------------------------------------------------------------------- 1 | @import "http://css-a"; 2 | @import 'data:text/css;base64,QGxheWVyIGxheWVyLWJldGE='; 3 | @import "http://css-b"; 4 | -------------------------------------------------------------------------------- /test/fixtures/layer-followed-by-ignore.css: -------------------------------------------------------------------------------- 1 | @layer layer-alpha; 2 | @import "http://css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/layer-followed-by-ignore.expected.css: -------------------------------------------------------------------------------- 1 | @layer layer-alpha; 2 | @import "http://css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/layer-import-atrules-anonymous.css: -------------------------------------------------------------------------------- 1 | @import url("layer-anonymous.css") layer screen; 2 | 3 | @import url("layer-anonymous.css") layer; 4 | 5 | @import url("layer-anonymous.css") layer(named-layer-1); 6 | 7 | @import "http://css-top-level" layer; 8 | -------------------------------------------------------------------------------- /test/fixtures/layer-import-atrules-anonymous.expected.css: -------------------------------------------------------------------------------- 1 | @import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0FpYUhSMGNEb3ZMMk56Y3lJZ2JHRjVaWElnYzJOeVpXVnUnIGxheWVy' layer; 2 | @import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0FpYUhSMGNEb3ZMMk56Y3kxaVlYSWlJR3hoZVdWeUlITmpjbVZsYmc9PScgbGF5ZXIoYmFyKQ==' layer; 3 | @import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0FpYUhSMGNEb3ZMMk56Y3lJZ2JHRjVaWEk9JyBsYXllcg==' layer; 4 | @import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0FpYUhSMGNEb3ZMMk56Y3kxaVlYSWlJR3hoZVdWeScgbGF5ZXIoYmFyKQ==' layer; 5 | @import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0FpYUhSMGNEb3ZMMk56Y3lJZ2JHRjVaWElvYm1GdFpXUXRiR0Y1WlhJdE1Taz0nIGxheWVy' layer; 6 | @import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0FpYUhSMGNEb3ZMMk56Y3kxaVlYSWlJR3hoZVdWeUtHNWhiV1ZrTFd4aGVXVnlMVEVwJyBsYXllcihiYXIp' layer; 7 | @import "http://css-top-level" layer; 8 | @media screen{ 9 | @layer{ 10 | @layer{ 11 | @media (min-width: 320px){ 12 | @layer{ 13 | @layer Z { 14 | body { 15 | color: cyan; 16 | } 17 | } 18 | } 19 | } 20 | } 21 | } 22 | } 23 | @media screen{ 24 | @layer{ 25 | @layer{ 26 | 27 | @layer Y { 28 | body { 29 | color: purple; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | @media screen{ 36 | @layer{ 37 | 38 | body { 39 | order: 1; 40 | } 41 | 42 | @media (min-width: 50rem) { 43 | body { 44 | order: 2; 45 | } 46 | } 47 | 48 | @keyframes RED_TO_BLUE { 49 | 0% { 50 | background-color: red; 51 | } 52 | 100% { 53 | background-color: blue; 54 | } 55 | } 56 | 57 | @supports (display: grid) { 58 | body { 59 | display: grid; 60 | order: 3; 61 | } 62 | } 63 | 64 | @layer A { 65 | body { 66 | order: 4; 67 | } 68 | } 69 | } 70 | } 71 | @layer{ 72 | @layer{ 73 | @media (min-width: 320px){ 74 | @layer{ 75 | @layer Z { 76 | body { 77 | color: cyan; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | @layer{ 85 | @layer{ 86 | 87 | @layer Y { 88 | body { 89 | color: purple; 90 | } 91 | } 92 | } 93 | } 94 | @layer{ 95 | 96 | body { 97 | order: 1; 98 | } 99 | 100 | @media (min-width: 50rem) { 101 | body { 102 | order: 2; 103 | } 104 | } 105 | 106 | @keyframes RED_TO_BLUE { 107 | 0% { 108 | background-color: red; 109 | } 110 | 100% { 111 | background-color: blue; 112 | } 113 | } 114 | 115 | @supports (display: grid) { 116 | body { 117 | display: grid; 118 | order: 3; 119 | } 120 | } 121 | 122 | @layer A { 123 | body { 124 | order: 4; 125 | } 126 | } 127 | } 128 | @layer named-layer-1{ 129 | @layer{ 130 | @media (min-width: 320px){ 131 | @layer{ 132 | @layer Z { 133 | body { 134 | color: cyan; 135 | } 136 | } 137 | } 138 | } 139 | } 140 | } 141 | @layer named-layer-1{ 142 | @layer{ 143 | 144 | @layer Y { 145 | body { 146 | color: purple; 147 | } 148 | } 149 | } 150 | } 151 | @layer named-layer-1{ 152 | 153 | body { 154 | order: 1; 155 | } 156 | 157 | @media (min-width: 50rem) { 158 | body { 159 | order: 2; 160 | } 161 | } 162 | 163 | @keyframes RED_TO_BLUE { 164 | 0% { 165 | background-color: red; 166 | } 167 | 100% { 168 | background-color: blue; 169 | } 170 | } 171 | 172 | @supports (display: grid) { 173 | body { 174 | display: grid; 175 | order: 3; 176 | } 177 | } 178 | 179 | @layer A { 180 | body { 181 | order: 4; 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /test/fixtures/layer-import-atrules.css: -------------------------------------------------------------------------------- 1 | @import url("layer.css") layer(imported-with-media) screen; 2 | 3 | @import url("layer.css") layer(imported-without-media); 4 | -------------------------------------------------------------------------------- /test/fixtures/layer-import-atrules.expected.css: -------------------------------------------------------------------------------- 1 | @media screen { 2 | @layer imported-with-media { 3 | @layer level-2 { 4 | @media (min-width: 320px) { 5 | @layer level-3 { 6 | @layer Z { 7 | body { 8 | color: cyan; 9 | } 10 | } 11 | } 12 | } 13 | } 14 | } 15 | } 16 | 17 | @media screen { 18 | @layer imported-with-media { 19 | @layer level-2 { 20 | 21 | @layer Y { 22 | body { 23 | color: purple; 24 | } 25 | } 26 | } 27 | } 28 | } 29 | 30 | @media screen { 31 | @layer imported-with-media { 32 | 33 | body { 34 | order: 1; 35 | } 36 | 37 | @media (min-width: 50rem) { 38 | body { 39 | order: 2; 40 | } 41 | } 42 | 43 | @keyframes RED_TO_BLUE { 44 | 0% { 45 | background-color: red; 46 | } 47 | 100% { 48 | background-color: blue; 49 | } 50 | } 51 | 52 | @supports (display: grid) { 53 | body { 54 | display: grid; 55 | order: 3; 56 | } 57 | } 58 | 59 | @layer A { 60 | body { 61 | order: 4; 62 | } 63 | } 64 | } 65 | } 66 | 67 | @layer imported-without-media { 68 | @layer level-2 { 69 | @media (min-width: 320px) { 70 | @layer level-3 { 71 | @layer Z { 72 | body { 73 | color: cyan; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | @layer imported-without-media { 82 | @layer level-2 { 83 | 84 | @layer Y { 85 | body { 86 | color: purple; 87 | } 88 | } 89 | } 90 | } 91 | 92 | @layer imported-without-media { 93 | 94 | body { 95 | order: 1; 96 | } 97 | 98 | @media (min-width: 50rem) { 99 | body { 100 | order: 2; 101 | } 102 | } 103 | 104 | @keyframes RED_TO_BLUE { 105 | 0% { 106 | background-color: red; 107 | } 108 | 100% { 109 | background-color: blue; 110 | } 111 | } 112 | 113 | @supports (display: grid) { 114 | body { 115 | display: grid; 116 | order: 3; 117 | } 118 | } 119 | 120 | @layer A { 121 | body { 122 | order: 4; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test/fixtures/layer-rule-grouping.expected.css: -------------------------------------------------------------------------------- 1 | @media screen { 2 | 3 | rule-one {} 4 | 5 | rule-two {} 6 | 7 | @media (min-width: 50rem) { 8 | rule-three {} 9 | 10 | rule-four {} 11 | } 12 | 13 | rule-five {} 14 | 15 | rule-six {} 16 | } 17 | 18 | @layer { 19 | 20 | rule-one {} 21 | 22 | rule-two {} 23 | 24 | @media (min-width: 50rem) { 25 | rule-three {} 26 | 27 | rule-four {} 28 | } 29 | 30 | rule-five {} 31 | 32 | rule-six {} 33 | } 34 | 35 | @layer named { 36 | 37 | rule-one {} 38 | 39 | rule-two {} 40 | 41 | @media (min-width: 50rem) { 42 | rule-three {} 43 | 44 | rule-four {} 45 | } 46 | 47 | rule-five {} 48 | 49 | rule-six {} 50 | } 51 | -------------------------------------------------------------------------------- /test/fixtures/layer-statement-with-conditions.css: -------------------------------------------------------------------------------- 1 | @import "layer-only.css" print; 2 | -------------------------------------------------------------------------------- /test/fixtures/layer-statement-with-conditions.expected.css: -------------------------------------------------------------------------------- 1 | @media print{ 2 | @layer layer-beta 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/layer.css: -------------------------------------------------------------------------------- 1 | @layer layer-alpha, layer-beta.one; 2 | 3 | @import "foo.css" layer(foo); 4 | @import 'bar.css' layer(bar); 5 | @import 'bar.css' layer(bar) level-1 and level-2; 6 | @import url(baz.css) layer(baz); 7 | @import url("foobar.css") layer(foobar); 8 | @import url("foo-layered.css") layer(foo-layered); 9 | 10 | content{} 11 | -------------------------------------------------------------------------------- /test/fixtures/layer.expected.css: -------------------------------------------------------------------------------- 1 | @layer layer-alpha, layer-beta.one; 2 | @layer foo{ 3 | foo{} 4 | } 5 | @layer bar{ 6 | bar{} 7 | } 8 | @media level-1 and level-2{ 9 | @layer bar{ 10 | bar{} 11 | } 12 | } 13 | @layer baz{ 14 | baz{} 15 | } 16 | @layer foobar{ 17 | foobar{} 18 | } 19 | @layer foo-layered{ 20 | @layer foo { 21 | foo {} 22 | } 23 | } 24 | content{} 25 | -------------------------------------------------------------------------------- /test/fixtures/media-charset.css: -------------------------------------------------------------------------------- 1 | @import "charset-content" level-1; 2 | -------------------------------------------------------------------------------- /test/fixtures/media-charset.expected.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | @media level-1{ 3 | .charset-content {} 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/media-combine.css: -------------------------------------------------------------------------------- 1 | @import "media-combine-nested.css" one-1; 2 | @import "media-combine-nested.css" or-left-1, or-right-1; 3 | @import "media-combine-nested.css" and-left-1 and and-right-1; 4 | @import "media-query-screen.css" screen; 5 | -------------------------------------------------------------------------------- /test/fixtures/media-combine.expected.css: -------------------------------------------------------------------------------- 1 | @media one-1 { 2 | 3 | @media one-2 {} 4 | 5 | @media or-left-2, or-right-2 {} 6 | 7 | @media and-left-2 and and-right-2 {} 8 | } 9 | 10 | @media or-left-1, or-right-1 { 11 | 12 | @media one-2 {} 13 | 14 | @media or-left-2, or-right-2 {} 15 | 16 | @media and-left-2 and and-right-2 {} 17 | } 18 | 19 | @media and-left-1 and and-right-1 { 20 | 21 | @media one-2 {} 22 | 23 | @media or-left-2, or-right-2 {} 24 | 25 | @media and-left-2 and and-right-2 {} 26 | } 27 | 28 | @media screen { 29 | 30 | @media screen { 31 | foo{} 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/fixtures/media-content.css: -------------------------------------------------------------------------------- 1 | @import "media-content-level-2" level-1; 2 | 3 | level-1 {} 4 | -------------------------------------------------------------------------------- /test/fixtures/media-content.expected.css: -------------------------------------------------------------------------------- 1 | @media level-1 { 2 | level-3 {} 3 | } 4 | 5 | @media level-1 { 6 | 7 | level-2 {} 8 | } 9 | 10 | level-1 {} 11 | -------------------------------------------------------------------------------- /test/fixtures/media-import.css: -------------------------------------------------------------------------------- 1 | @import "media-import-level-2" level-1; 2 | 3 | @import "//" level-1; 4 | -------------------------------------------------------------------------------- /test/fixtures/media-import.expected.css: -------------------------------------------------------------------------------- 1 | @import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0FpTHk4aUlHeGxkbVZzTFRFPScgbGV2ZWwtMw==' level-2; 2 | 3 | @import 'data:text/css;base64,QGltcG9ydCAiLy8iIGxldmVsLTE=' level-2; 4 | 5 | @import "//" level-1; 6 | -------------------------------------------------------------------------------- /test/fixtures/media-query.css: -------------------------------------------------------------------------------- 1 | @import "media-query-level-2" level-1; 2 | 3 | @media level-1 {} 4 | -------------------------------------------------------------------------------- /test/fixtures/media-query.expected.css: -------------------------------------------------------------------------------- 1 | @media level-1 { 2 | @media level-2 { 3 | @media level-3 {} 4 | } 5 | } 6 | 7 | @media level-1 { 8 | 9 | @media level-2 {} 10 | } 11 | 12 | @media level-1 {} 13 | -------------------------------------------------------------------------------- /test/fixtures/no-duplicate.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import "foo.css"; 3 | @import "foo-duplicate.css"; 4 | 5 | @import "foo.css" screen; 6 | @import "foo-duplicate2" screen; 7 | 8 | @import "proxy-file/index.css"; 9 | @import "proxy-file/sub-directory/index.css"; 10 | 11 | content{} 12 | -------------------------------------------------------------------------------- /test/fixtures/no-duplicate.expected.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | @media screen{ 3 | foo{} 4 | } 5 | proxy {} 6 | import {} 7 | content{} 8 | -------------------------------------------------------------------------------- /test/fixtures/order.css: -------------------------------------------------------------------------------- 1 | @import "foo-first.css"; 2 | @import "foo-second.css"; 3 | 4 | .baz { 5 | color: green; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/order.expected.css: -------------------------------------------------------------------------------- 1 | bar{} 2 | 3 | foo.first{ 4 | color: red; 5 | } 6 | 7 | foo.second{ 8 | color: blue; 9 | } 10 | 11 | .baz { 12 | color: green; 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/plugins.css: -------------------------------------------------------------------------------- 1 | @import 'foo/index.css'; 2 | @import 'bar.css'; 3 | @level-1-1 {} 4 | @level-1-2 {} 5 | -------------------------------------------------------------------------------- /test/fixtures/plugins.expected.css: -------------------------------------------------------------------------------- 1 | foo-converted {} 2 | @level-1-1 {} 3 | @level-1-2 {} 4 | -------------------------------------------------------------------------------- /test/fixtures/resolve-custom-modules.css: -------------------------------------------------------------------------------- 1 | @import "shared-fake"; 2 | @import "shared-auto"; 3 | @import "shared-nest"; 4 | @import "shared-by-hand/style.css"; 5 | @import "shared-use-dep"; 6 | @import "shared-use-dep-too"; 7 | @import "shared-use-dep" screen; 8 | -------------------------------------------------------------------------------- /test/fixtures/resolve-custom-modules.expected.css: -------------------------------------------------------------------------------- 1 | .shared-fake{} 2 | .shared-auto{} 3 | .shared-nested{} 4 | .shared-byHand{} 5 | .shared-dep{} 6 | @media screen{ 7 | .shared-dep{} 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/resolve-cwd.css: -------------------------------------------------------------------------------- 1 | @import "test/fixtures/imports/foo.css"; 2 | @import "test/fixtures/imports/foo-recursive.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/resolve-cwd.expected.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | bar{} 3 | foo.recursive{} 4 | -------------------------------------------------------------------------------- /test/fixtures/resolve-from.css: -------------------------------------------------------------------------------- 1 | @import "imports/foo.css"; 2 | @import "imports/foo-recursive.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/resolve-from.expected.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | bar{} 3 | foo.recursive{} 4 | -------------------------------------------------------------------------------- /test/fixtures/resolve-local-modules.css: -------------------------------------------------------------------------------- 1 | @import "test/fixtures/imports/modules/simple"; 2 | @import "test/fixtures/imports/modules/empty"; 3 | @import "test/fixtures/imports/modules/style"; 4 | @import "test/fixtures/imports/modules/main"; 5 | @import "test/fixtures/imports/modules/main-js"; 6 | @import "test/fixtures/imports/modules/main-style"; 7 | -------------------------------------------------------------------------------- /test/fixtures/resolve-local-modules.expected.css: -------------------------------------------------------------------------------- 1 | simple {} 2 | empty {} 3 | style {} 4 | main {} 5 | main-js {} 6 | main-styled {} 7 | -------------------------------------------------------------------------------- /test/fixtures/resolve-modules.css: -------------------------------------------------------------------------------- 1 | @import "fake"; 2 | @import "auto"; 3 | @import "nest"; 4 | @import "by-hand/style.css"; 5 | @import "use-dep"; 6 | @import "use-dep-too"; 7 | @import "use-dep" screen; 8 | 9 | @import "web-fake"; 10 | @import "web-auto"; 11 | @import "web-nest"; 12 | @import "web-by-hand/style.css"; 13 | @import "web-use-dep"; 14 | @import "web-use-dep-too"; 15 | @import "web-use-dep" screen; 16 | -------------------------------------------------------------------------------- /test/fixtures/resolve-modules.expected.css: -------------------------------------------------------------------------------- 1 | .fake{} 2 | .auto{} 3 | .nested{} 4 | .byHand{} 5 | .dep{} 6 | @media screen{ 7 | .dep{} 8 | } 9 | .web-fake{} 10 | .web-auto{} 11 | .web-nested{} 12 | .web-byHand{} 13 | .web-dep{} 14 | @media screen{ 15 | .web-dep{} 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/resolve-npm-subpackages.css: -------------------------------------------------------------------------------- 1 | @import "modularized-css/foo"; 2 | @import "modularized-css/foo/boo/boomod.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/resolve-npm-subpackages.expected.css: -------------------------------------------------------------------------------- 1 | .i-pity-the-foo{} 2 | .i-pity-the-boo{} 3 | -------------------------------------------------------------------------------- /test/fixtures/resolve-path-cwd.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import "foo-recursive.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/resolve-path-cwd.expected.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | bar{} 3 | foo.recursive{} 4 | -------------------------------------------------------------------------------- /test/fixtures/resolve-path-modules.css: -------------------------------------------------------------------------------- 1 | @import "simple"; 2 | @import "empty"; 3 | @import "style"; 4 | @import "main"; 5 | @import "main-js"; 6 | @import "main-style"; 7 | -------------------------------------------------------------------------------- /test/fixtures/resolve-path-modules.expected.css: -------------------------------------------------------------------------------- 1 | simple {} 2 | empty {} 3 | style {} 4 | main {} 5 | main-js {} 6 | main-styled {} 7 | -------------------------------------------------------------------------------- /test/fixtures/resolve-path-root.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import "foo-recursive.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/resolve-path-root.expected.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | bar{} 3 | foo.recursive{} 4 | -------------------------------------------------------------------------------- /test/fixtures/resolve-root.css: -------------------------------------------------------------------------------- 1 | @import "imports/foo.css"; 2 | @import "imports/foo-recursive.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/resolve-root.expected.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | bar{} 3 | foo.recursive{} 4 | -------------------------------------------------------------------------------- /test/fixtures/same.css: -------------------------------------------------------------------------------- 1 | @import "foo/index.css"; 2 | @import "bar/index.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/same.expected.css: -------------------------------------------------------------------------------- 1 | foo {} 2 | bar {} 3 | -------------------------------------------------------------------------------- /test/fixtures/scss-parser.css: -------------------------------------------------------------------------------- 1 | @import "inline-comment.scss"; 2 | -------------------------------------------------------------------------------- /test/fixtures/scss-parser.expected.css: -------------------------------------------------------------------------------- 1 | /* inline comment*/ 2 | -------------------------------------------------------------------------------- /test/fixtures/scss-syntax.css: -------------------------------------------------------------------------------- 1 | @import "inline-comment.scss"; 2 | -------------------------------------------------------------------------------- /test/fixtures/scss-syntax.expected.css: -------------------------------------------------------------------------------- 1 | // inline comment 2 | -------------------------------------------------------------------------------- /test/fixtures/simple.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | @import "foo.css" screen; 3 | @import 'bar.css'; 4 | @import 'bar.css' screen; 5 | @import url(baz.css); 6 | @import url(baz.css) screen; 7 | @import url("foobar.css"); 8 | @import url("foobar.css") screen and (min-width: 25em); 9 | @import url('foobarbaz.css'); 10 | @import url('foobarbaz.css') print,screen and (min-width: 25em); 11 | 12 | content{} 13 | -------------------------------------------------------------------------------- /test/fixtures/simple.expected.css: -------------------------------------------------------------------------------- 1 | foo{} 2 | @media screen{ 3 | foo{} 4 | } 5 | bar{} 6 | @media screen{ 7 | bar{} 8 | } 9 | baz{} 10 | @media screen{ 11 | baz{} 12 | } 13 | foobar{} 14 | @media screen and (min-width: 25em){ 15 | foobar{} 16 | } 17 | foobarbaz{} 18 | @media print,screen and (min-width: 25em){ 19 | foobarbaz{} 20 | } 21 | content{} 22 | -------------------------------------------------------------------------------- /test/fixtures/subpath.css: -------------------------------------------------------------------------------- 1 | @import "subpath"; 2 | -------------------------------------------------------------------------------- /test/fixtures/subpath.expected.css: -------------------------------------------------------------------------------- 1 | .foo { 2 | color: green; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/sugar-import-css.expected.css: -------------------------------------------------------------------------------- 1 | bar{} 2 | .sugar{ 3 | color: white 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/sugar-import-css.sss: -------------------------------------------------------------------------------- 1 | @import "bar.css" 2 | .sugar 3 | color: white 4 | -------------------------------------------------------------------------------- /test/fixtures/sugar.expected.css: -------------------------------------------------------------------------------- 1 | .sugarbar { 2 | color: blue 3 | } 4 | .sugar { 5 | color: white 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/sugar.sss: -------------------------------------------------------------------------------- 1 | @import "sugarbar.sss" 2 | .sugar 3 | color: white 4 | -------------------------------------------------------------------------------- /test/fixtures/supports-condition-import.css: -------------------------------------------------------------------------------- 1 | @import "supports-condition-level-2" layer supports(filter: blur(1px)) (width > 100px); 2 | 3 | @supports (level: 1) {} 4 | -------------------------------------------------------------------------------- /test/fixtures/supports-condition-import.expected.css: -------------------------------------------------------------------------------- 1 | @media (width > 100px) { 2 | @supports (filter: blur(1px)) { 3 | @layer { 4 | @media screen { 5 | @supports (color: green) { 6 | @layer { 7 | @supports (level: 3) {} 8 | } 9 | } 10 | } 11 | } 12 | } 13 | } 14 | 15 | @media (width > 100px) { 16 | @supports (filter: blur(1px)) { 17 | @layer { 18 | 19 | @supports (level: 2) {} 20 | } 21 | } 22 | } 23 | 24 | @supports (level: 1) {} 25 | -------------------------------------------------------------------------------- /test/fixtures/syntax-error.css: -------------------------------------------------------------------------------- 1 | @import "syntax-error.css"; 2 | 3 | syntax.error { 4 | /* Error isn't here, it's in the imported file */ 5 | color: green; 6 | } 7 | -------------------------------------------------------------------------------- /test/helpers/ast-checker.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const astCheckerPlugin = () => { 4 | return { 5 | postcssPlugin: "ast-checker-plugin", 6 | OnceExit(root) { 7 | root.walkAtRules(node => { 8 | if (typeof node.params !== "string") { 9 | throw node.error( 10 | `Params must be of type 'string', found '${typeof node.params}' instead`, 11 | ) 12 | } 13 | 14 | if (typeof node.type !== "string") { 15 | throw node.error( 16 | `Type must be of type 'string', found '${typeof node.type}' instead`, 17 | ) 18 | } 19 | 20 | if (node.type !== "atrule") { 21 | throw node.error( 22 | `Type must be 'atrule', found '${node.type}' instead`, 23 | ) 24 | } 25 | 26 | if (typeof node.name !== "string") { 27 | throw node.error( 28 | `Name must be of type 'string', found '${typeof node.name}' instead`, 29 | ) 30 | } 31 | 32 | if (node.nodes && !Array.isArray(node.nodes)) { 33 | throw node.error( 34 | `Nodes must be of type 'Array' when it is present, found '${typeof node.nodes}' instead`, 35 | ) 36 | } 37 | 38 | if (!("parent" in node)) { 39 | throw node.error("AtRule must have a 'parent' property") 40 | } 41 | }) 42 | }, 43 | } 44 | } 45 | 46 | astCheckerPlugin.postcss = true 47 | 48 | module.exports = astCheckerPlugin 49 | -------------------------------------------------------------------------------- /test/helpers/check-fixture.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | // builtin tooling 4 | const fs = require("fs") 5 | 6 | // external tooling 7 | const postcss = require("postcss") 8 | 9 | // plugin 10 | const atImport = require("../..") 11 | const astCheckerPlugin = require("./ast-checker") 12 | 13 | function read(name, ext) { 14 | ext = ext || ".css" 15 | return fs.readFileSync(`test/fixtures/${name}${ext}`, "utf8") 16 | } 17 | 18 | module.exports = function (t, file, opts, postcssOpts, warnings) { 19 | opts = { path: "test/fixtures/imports", ...opts } 20 | postcssOpts = { from: undefined, ...postcssOpts } 21 | if (typeof file === "string") file = { name: file, ext: ".css" } 22 | const { name, ext } = file 23 | 24 | return postcss([atImport(opts), astCheckerPlugin()]) 25 | .process(read(name, ext), postcssOpts || {}) 26 | .then(result => { 27 | const actual = result.css 28 | const expected = read(`${name}.expected`) 29 | // handy thing: checkout actual in the *.actual.css file 30 | fs.writeFile(`test/fixtures/${name}.actual.css`, actual, err => { 31 | // eslint-disable-next-line no-console 32 | if (err) console.warn(`Warning: ${err}; not fatal, continuing`) 33 | }) 34 | t.is(actual, expected) 35 | if (!warnings) warnings = [] 36 | result.warnings().forEach((warning, index) => { 37 | t.is( 38 | warning.text, 39 | warnings[index], 40 | `unexpected warning: "${warning.text}"`, 41 | ) 42 | }) 43 | 44 | t.is( 45 | warnings.length, 46 | result.warnings().length, 47 | `expected ${warnings.length} warning(s), got ${ 48 | result.warnings().length 49 | }`, 50 | ) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /test/import-events.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // builtin tooling 3 | const { readFileSync } = require("fs") 4 | const { resolve } = require("path") 5 | 6 | // external tooling 7 | const test = require("ava") 8 | const postcss = require("postcss") 9 | 10 | // plugin 11 | const atImport = require("..") 12 | 13 | test("should add dependency message for each import", t => { 14 | return postcss() 15 | .use(atImport({ path: "test/fixtures/imports" })) 16 | .process(readFileSync("test/fixtures/media-import.css"), { 17 | from: "test/fixtures/media-import.css", 18 | }) 19 | .then(result => { 20 | const deps = result.messages.filter( 21 | message => message.type === "dependency", 22 | ) 23 | const expected = [ 24 | { 25 | type: "dependency", 26 | plugin: "postcss-import", 27 | file: resolve("test/fixtures/imports/media-import-level-2.css"), 28 | parent: resolve("test/fixtures/media-import.css"), 29 | }, 30 | { 31 | type: "dependency", 32 | plugin: "postcss-import", 33 | file: resolve("test/fixtures/imports/media-import-level-3.css"), 34 | parent: resolve("test/fixtures/imports/media-import-level-2.css"), 35 | }, 36 | ] 37 | t.deepEqual(deps, expected) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/import.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // builtin tooling 3 | const { readFileSync } = require("fs") 4 | const path = require("path") 5 | 6 | // external tooling 7 | const test = require("ava") 8 | const postcss = require("postcss") 9 | 10 | // plugin 11 | const atImport = require("..") 12 | 13 | // internal tooling 14 | const checkFixture = require("./helpers/check-fixture") 15 | 16 | test("should import stylesheets", checkFixture, "simple") 17 | 18 | test("should not import a stylesheet twice", checkFixture, "no-duplicate") 19 | 20 | test( 21 | "should be able to import a stylesheet twice", 22 | checkFixture, 23 | "duplicates", 24 | { 25 | skipDuplicates: false, 26 | }, 27 | ) 28 | 29 | test( 30 | "should be able to import a stylesheet with cyclical dependencies", 31 | checkFixture, 32 | "cyclical", 33 | { 34 | skipDuplicates: false, 35 | }, 36 | ) 37 | 38 | test( 39 | "should be able to import a stylesheet with cyclical dependencies and skip duplicates is true", 40 | checkFixture, 41 | "cyclical-skip-duplicates", 42 | { 43 | skipDuplicates: true, 44 | }, 45 | ) 46 | 47 | test("should import stylesheets with same content", checkFixture, "same") 48 | 49 | test("should ignore & adjust external import", checkFixture, "ignore") 50 | 51 | test("should not fail with only one absolute import", t => { 52 | const base = "@import url(http://)" 53 | return postcss() 54 | .use(atImport()) 55 | .process(base, { from: undefined }) 56 | .then(result => { 57 | t.is(result.warnings().length, 0) 58 | t.is(result.css, base) 59 | }) 60 | }) 61 | 62 | test("should not fail with absolute and local import", t => { 63 | return postcss() 64 | .use(atImport()) 65 | .process( 66 | "@import url('http://');\n@import 'test/fixtures/imports/foo.css';", 67 | { from: undefined }, 68 | ) 69 | .then(result => t.is(result.css, "@import url('http://');\nfoo{}")) 70 | }) 71 | 72 | test("should keep @charset first", t => { 73 | const base = '@charset "UTF-8";\n@import url(http://);' 74 | return postcss() 75 | .use(atImport()) 76 | .process(base, { from: undefined }) 77 | .then(result => { 78 | t.is(result.warnings().length, 0) 79 | t.is(result.css, base) 80 | }) 81 | }) 82 | 83 | test( 84 | "should handle multiple @charset statements", 85 | checkFixture, 86 | "charset-import", 87 | ) 88 | 89 | test("should error if incompatible @charset statements", t => { 90 | t.plan(2) 91 | const file = "test/fixtures/charset-error.css" 92 | return postcss() 93 | .use(atImport()) 94 | .process(readFileSync(file), { from: file }) 95 | .catch(err => { 96 | t.truthy(err) 97 | t.regex( 98 | err.message, 99 | /Incompatible @charset statements:.+specified in.+specified in.+/s, 100 | ) 101 | }) 102 | }) 103 | 104 | test("should error when file not found", t => { 105 | t.plan(1) 106 | const file = "test/fixtures/imports/import-missing.css" 107 | return postcss() 108 | .use(atImport()) 109 | .process(readFileSync(file), { from: file }) 110 | .catch(err => t.truthy(err)) 111 | }) 112 | 113 | test("should contain a correct sourcemap", t => { 114 | return postcss() 115 | .use(atImport()) 116 | .process(readFileSync("test/sourcemap/in.css"), { 117 | from: "test/sourcemap/in.css", 118 | to: null, 119 | map: { inline: false }, 120 | }) 121 | .then(result => { 122 | t.is( 123 | result.map.toString(), 124 | readFileSync( 125 | process.platform === "win32" 126 | ? "test/sourcemap/out.css.win.map" 127 | : "test/sourcemap/out.css.map", 128 | "utf8", 129 | ).trim(), 130 | ) 131 | }) 132 | }) 133 | 134 | test("inlined @import should keep PostCSS AST references clean", t => { 135 | return postcss() 136 | .use(atImport()) 137 | .process("@import 'test/fixtures/imports/foo.css';\nbar{}", { 138 | from: undefined, 139 | }) 140 | .then(result => { 141 | result.root.nodes.forEach(node => { 142 | t.is(result.root, node.parent) 143 | }) 144 | }) 145 | }) 146 | 147 | test( 148 | "should work with empty files", 149 | checkFixture, 150 | "empty-and-useless", 151 | { path: "test/fixtures/imports" }, 152 | null, 153 | [`${path.resolve("test/fixtures/imports/empty.css")} is empty`], 154 | ) 155 | 156 | test( 157 | "should be able to disable warnings for empty files", 158 | checkFixture, 159 | "empty-and-useless", 160 | { path: "test/fixtures/imports", warnOnEmpty: false }, 161 | ) 162 | 163 | test("should work with no styles without throwing an error", t => { 164 | return postcss() 165 | .use(atImport()) 166 | .process("", { from: undefined }) 167 | .then(result => { 168 | t.is(result.warnings().length, 0) 169 | }) 170 | }) 171 | -------------------------------------------------------------------------------- /test/layer.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | 5 | // internal tooling 6 | const checkFixture = require("./helpers/check-fixture") 7 | 8 | test("should resolve layers of import statements", checkFixture, "layer") 9 | 10 | test( 11 | "should correctly wrap imported at rules in layers", 12 | checkFixture, 13 | "layer-import-atrules", 14 | ) 15 | 16 | test( 17 | "should correctly wrap imported at rules in anonymous layers", 18 | checkFixture, 19 | "layer-import-atrules-anonymous", 20 | ) 21 | 22 | test( 23 | "should correctly handle duplicate anonymous imports", 24 | checkFixture, 25 | "layer-duplicate-anonymous-imports", 26 | { 27 | skipDuplicates: false, 28 | }, 29 | ) 30 | 31 | test( 32 | "should correctly handle duplicate anonymous imports and skip duplicates is true", 33 | checkFixture, 34 | "layer-duplicate-anonymous-imports-skip", 35 | ) 36 | 37 | test( 38 | "should correctly handle layer statements followed by ignored imports", 39 | checkFixture, 40 | "layer-followed-by-ignore", 41 | ) 42 | 43 | test( 44 | "should correctly handle layer statements followed by ignored imports in conditional imports", 45 | checkFixture, 46 | "layer-followed-by-ignore-with-conditions", 47 | ) 48 | 49 | test( 50 | "should correctly handle layer statements followed by ignored imports in unconditional imports", 51 | checkFixture, 52 | "layer-followed-by-ignore-without-conditions", 53 | ) 54 | 55 | test( 56 | "should correctly handle layer statements in conditional imports", 57 | checkFixture, 58 | "layer-statement-with-conditions", 59 | ) 60 | -------------------------------------------------------------------------------- /test/lint.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | const postcss = require("postcss") 5 | 6 | // plugin 7 | const atImport = require("..") 8 | 9 | const processor = postcss().use(atImport()) 10 | 11 | test("should warn when not @charset and not @import statement before", t => { 12 | return Promise.all([ 13 | processor.process(`a {} @import "";`, { from: undefined }), 14 | processor.process(`@media {} @import "";`, { from: undefined }), 15 | ]).then(results => { 16 | results.forEach(result => { 17 | const warnings = result.warnings() 18 | t.is(warnings.length, 1) 19 | t.is( 20 | warnings[0].text, 21 | "@import must precede all other statements (besides @charset or empty @layer)", 22 | ) 23 | }) 24 | }) 25 | }) 26 | 27 | test("should warn about all imports after some other CSS declaration", t => { 28 | return processor 29 | .process( 30 | ` 31 | a {} 32 | @import "a.css"; 33 | @import "b.css"; 34 | `, 35 | { from: undefined }, 36 | ) 37 | .then(result => { 38 | t.plan(2) 39 | result.warnings().forEach(warning => { 40 | t.is( 41 | warning.text, 42 | "@import must precede all other statements (besides @charset or empty @layer)", 43 | ) 44 | }) 45 | }) 46 | }) 47 | 48 | test("should warn if non-empty @layer before @import", t => { 49 | return processor 50 | .process(`@layer { a {} } @import "a.css";`, { from: undefined }) 51 | .then(result => { 52 | t.plan(1) 53 | result.warnings().forEach(warning => { 54 | t.is( 55 | warning.text, 56 | "@import must precede all other statements (besides @charset or empty @layer)", 57 | ) 58 | }) 59 | }) 60 | }) 61 | 62 | test("should warn when import statements are not consecutive", t => { 63 | return processor 64 | .process( 65 | ` 66 | @import "bar.css"; 67 | @layer a; 68 | @import "bar.css"; 69 | `, 70 | { from: "test/fixtures/imports/foo.css" }, 71 | ) 72 | .then(result => { 73 | t.plan(1) 74 | result.warnings().forEach(warning => { 75 | t.is( 76 | warning.text, 77 | "@import must precede all other statements (besides @charset or empty @layer)", 78 | ) 79 | }) 80 | }) 81 | }) 82 | 83 | test("should not warn if empty @layer before @import", t => { 84 | return processor 85 | .process(`@layer a; @import "";`, { from: undefined }) 86 | .then(result => { 87 | const warnings = result.warnings() 88 | t.is(warnings.length, 1) 89 | t.is(warnings[0].text, `Unable to find uri in '@import ""'`) 90 | }) 91 | }) 92 | 93 | test("should not warn if comments before @import", t => { 94 | return processor 95 | .process(`/* skipped comment */ @import "";`, { from: undefined }) 96 | .then(result => { 97 | const warnings = result.warnings() 98 | t.is(warnings.length, 1) 99 | t.is(warnings[0].text, `Unable to find uri in '@import ""'`) 100 | }) 101 | }) 102 | 103 | test("should warn if something before comments", t => { 104 | return processor 105 | .process(`a{} /* skipped comment */ @import "";`, { from: undefined }) 106 | .then(result => { 107 | t.is(result.warnings().length, 1) 108 | }) 109 | }) 110 | 111 | test("should not warn when @charset or @import statement before", t => { 112 | return Promise.all([ 113 | processor.process(`@import "bar.css"; @import "bar.css";`, { 114 | from: "test/fixtures/imports/foo.css", 115 | }), 116 | processor.process(`@charset "bar.css"; @import "bar.css";`, { 117 | from: "test/fixtures/imports/foo.css", 118 | }), 119 | ]).then(results => { 120 | results.forEach(result => { 121 | t.is(result.warnings().length, 0) 122 | }) 123 | }) 124 | }) 125 | 126 | test("should warn when @charset is not first", t => { 127 | return Promise.all([ 128 | processor.process(`a {} @charset "utf-8";`, { from: undefined }), 129 | processor.process(`@media {} @charset "utf-8";`, { from: undefined }), 130 | processor.process(`/* foo */ @charset "utf-8";`, { from: undefined }), 131 | processor.process(`@import "bar.css"; @charset "utf-8";`, { 132 | from: "test/fixtures/imports/foo.css", 133 | }), 134 | ]).then(results => { 135 | results.forEach(result => { 136 | const warnings = result.warnings() 137 | t.is(warnings.length, 1) 138 | t.is(warnings[0].text, "@charset must precede all other statements") 139 | }) 140 | }) 141 | }) 142 | 143 | test("should warn when a user didn't close an import with ;", t => { 144 | return processor 145 | .process(`@import url('http://') :root{}`, { from: undefined }) 146 | .then(result => { 147 | const warnings = result.warnings() 148 | t.is(warnings.length, 1) 149 | t.is( 150 | warnings[0].text, 151 | "It looks like you didn't end your @import statement correctly. " + 152 | "Child nodes are attached to it.", 153 | ) 154 | }) 155 | }) 156 | 157 | test("should warn on invalid url", t => { 158 | return processor 159 | .process( 160 | ` 161 | @import foo-bar; 162 | @import ; 163 | @import ''; 164 | @import ""; 165 | @import url(); 166 | @import url(''); 167 | @import url(""); 168 | @import layer url(""); 169 | @import supports(foo: bar) url(""); 170 | `, 171 | { from: undefined }, 172 | ) 173 | .then(result => { 174 | const warnings = result.warnings() 175 | t.is(warnings.length, 9) 176 | t.is(warnings[0].text, `Unable to find uri in '@import foo-bar'`) 177 | t.is(warnings[1].text, `Unable to find uri in '@import '`) 178 | t.is(warnings[2].text, `Unable to find uri in '@import '''`) 179 | t.is(warnings[3].text, `Unable to find uri in '@import ""'`) 180 | t.is(warnings[4].text, `Unable to find uri in '@import url()'`) 181 | t.is(warnings[5].text, `Unable to find uri in '@import url('')'`) 182 | t.is(warnings[6].text, `Unable to find uri in '@import url("")'`) 183 | t.is(warnings[7].text, `Unable to find uri in '@import layer url("")'`) 184 | t.is( 185 | warnings[8].text, 186 | `Unable to find uri in '@import supports(foo: bar) url("")'`, 187 | ) 188 | }) 189 | }) 190 | 191 | test("should warn on duplicate url's", t => { 192 | return processor 193 | .process( 194 | ` 195 | @import 'foo' "bar"; 196 | @import "foo" url(bar); 197 | @import url(foo) "bar"; 198 | @import url('foo') url(bar); 199 | @import url('foo') layer url(bar); 200 | @import url('foo') layer(foo) "bar"; 201 | @import url('foo') supports(foo: bar) url(bar); 202 | `, 203 | { from: undefined }, 204 | ) 205 | .then(result => { 206 | const warnings = result.warnings() 207 | t.is(warnings.length, 7) 208 | t.is(warnings[0].text, `Multiple url's in '@import 'foo' "bar"'`) 209 | t.is(warnings[1].text, `Multiple url's in '@import "foo" url(bar)'`) 210 | t.is(warnings[2].text, `Multiple url's in '@import url(foo) "bar"'`) 211 | t.is(warnings[3].text, `Multiple url's in '@import url('foo') url(bar)'`) 212 | t.is( 213 | warnings[4].text, 214 | `Multiple url's in '@import url('foo') layer url(bar)'`, 215 | ) 216 | t.is( 217 | warnings[5].text, 218 | `Multiple url's in '@import url('foo') layer(foo) "bar"'`, 219 | ) 220 | t.is( 221 | warnings[6].text, 222 | `Multiple url's in '@import url('foo') supports(foo: bar) url(bar)'`, 223 | ) 224 | }) 225 | }) 226 | 227 | test("should warn on multiple layer clauses", t => { 228 | return processor 229 | .process( 230 | ` 231 | @import url('foo') layer layer(bar); 232 | `, 233 | { from: undefined }, 234 | ) 235 | .then(result => { 236 | const warnings = result.warnings() 237 | t.is(warnings.length, 1) 238 | t.is( 239 | warnings[0].text, 240 | `Multiple layers in '@import url('foo') layer layer(bar)'`, 241 | ) 242 | }) 243 | }) 244 | 245 | test("should warn on when support conditions precede layer clauses", t => { 246 | return processor 247 | .process( 248 | ` 249 | @import url('foo') supports(selector(&)) layer(bar); 250 | `, 251 | { from: undefined }, 252 | ) 253 | .then(result => { 254 | const warnings = result.warnings() 255 | t.is(warnings.length, 1) 256 | t.is( 257 | warnings[0].text, 258 | `layers must be defined before support conditions in '@import url('foo') supports(selector(&)) layer(bar)'`, 259 | ) 260 | }) 261 | }) 262 | 263 | test("should warn on multiple support conditions", t => { 264 | return processor 265 | .process( 266 | ` 267 | @import url('foo') supports(selector(&)) supports((display: grid)); 268 | `, 269 | { from: undefined }, 270 | ) 271 | .then(result => { 272 | const warnings = result.warnings() 273 | t.is(warnings.length, 1) 274 | t.is( 275 | warnings[0].text, 276 | `Multiple support conditions in '@import url('foo') supports(selector(&)) supports((display: grid))'`, 277 | ) 278 | }) 279 | }) 280 | 281 | test("should not warn when a user closed an import with ;", t => { 282 | return processor 283 | .process(`@import url('http://');`, { from: undefined }) 284 | .then(result => { 285 | t.is(result.warnings().length, 0) 286 | }) 287 | }) 288 | -------------------------------------------------------------------------------- /test/media.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | 5 | // internal tooling 6 | const checkFixture = require("./helpers/check-fixture") 7 | 8 | test( 9 | "should resolve media queries of import statements", 10 | checkFixture, 11 | "media-import", 12 | ) 13 | 14 | test("should resolve media queries", checkFixture, "media-query") 15 | 16 | test( 17 | "should resolve content inside import with media queries", 18 | checkFixture, 19 | "media-content", 20 | ) 21 | 22 | test( 23 | "should resolve media query imports with charset", 24 | checkFixture, 25 | "media-charset", 26 | ) 27 | 28 | test("should correctly combine media queries", checkFixture, "media-combine") 29 | -------------------------------------------------------------------------------- /test/node_modules/auto/index.css: -------------------------------------------------------------------------------- 1 | .auto{} 2 | -------------------------------------------------------------------------------- /test/node_modules/by-hand/style.css: -------------------------------------------------------------------------------- 1 | .byHand{} 2 | -------------------------------------------------------------------------------- /test/node_modules/dep/index.css: -------------------------------------------------------------------------------- 1 | .dep{} 2 | -------------------------------------------------------------------------------- /test/node_modules/fake/main.css: -------------------------------------------------------------------------------- 1 | .fake{} 2 | -------------------------------------------------------------------------------- /test/node_modules/fake/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style": "main.css" 3 | } 4 | -------------------------------------------------------------------------------- /test/node_modules/modularized-css/foo/boo/boomod.css: -------------------------------------------------------------------------------- 1 | .i-pity-the-boo{} 2 | -------------------------------------------------------------------------------- /test/node_modules/modularized-css/foo/index.css: -------------------------------------------------------------------------------- 1 | .i-pity-the-foo{} 2 | -------------------------------------------------------------------------------- /test/node_modules/nest/index.css: -------------------------------------------------------------------------------- 1 | @import "ed"; 2 | -------------------------------------------------------------------------------- /test/node_modules/nest/node_modules/ed/index.css: -------------------------------------------------------------------------------- 1 | .nested{} 2 | -------------------------------------------------------------------------------- /test/node_modules/use-dep-too/index.css: -------------------------------------------------------------------------------- 1 | @import "dep"; 2 | -------------------------------------------------------------------------------- /test/node_modules/use-dep/index.css: -------------------------------------------------------------------------------- 1 | @import "dep"; 2 | -------------------------------------------------------------------------------- /test/order.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | 5 | // internal tooling 6 | const checkFixture = require("./helpers/check-fixture") 7 | 8 | test(`should order nested imports correctly`, t => { 9 | let first = true 10 | const path = require("path") 11 | 12 | return checkFixture(t, "order", { 13 | path: "test/fixtures/imports", 14 | resolve: id => { 15 | return new Promise(res => { 16 | const doResolve = () => res(path.resolve("test/fixtures/imports", id)) 17 | 18 | if (first) { 19 | // Delay the first import so the second gets loaded first 20 | setTimeout(doResolve, 100) 21 | first = false 22 | } else doResolve() 23 | }) 24 | }, 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /test/plugins.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | const postcss = require("postcss") 5 | 6 | // plugin 7 | const atImport = require("..") 8 | 9 | // internal tooling 10 | const checkFixture = require("./helpers/check-fixture") 11 | 12 | test("should apply plugins to root", t => { 13 | const atRules = [] 14 | const rules = [] 15 | return checkFixture(t, "plugins", { 16 | plugins: [ 17 | css => { 18 | css.walk(node => { 19 | if (node.type === "rule") { 20 | rules.push(node.selector) 21 | if (node.selector === "bar") node.remove() 22 | else node.selector += "-converted" 23 | } 24 | if (node.type === "atrule") atRules.push(node.name) 25 | }) 26 | }, 27 | ], 28 | }).then(() => { 29 | t.deepEqual(atRules, ["import"]) 30 | t.deepEqual(rules, ["foo", "bar"]) 31 | }) 32 | }) 33 | 34 | test("should error when value is not an array", t => { 35 | return postcss() 36 | .use(atImport({ plugins: "foo" })) 37 | .process("", { from: undefined }) 38 | .catch(error => t.regex(error.message, /plugins option must be an array/)) 39 | }) 40 | 41 | test("should remain silent when value is an empty array", t => { 42 | return postcss() 43 | .use(atImport({ plugins: [] })) 44 | .process("", { from: undefined }) 45 | .then(result => t.is(result.css, "")) 46 | }) 47 | -------------------------------------------------------------------------------- /test/resolve.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | const fs = require("fs") 5 | const path = require("path") 6 | 7 | // internal tooling 8 | const checkFixture = require("./helpers/check-fixture") 9 | 10 | test("should resolve relative to cwd", checkFixture, "resolve-cwd", { 11 | path: null, 12 | }) 13 | 14 | test(`should resolve relative to 'root' option`, checkFixture, "resolve-root", { 15 | root: "test/fixtures", 16 | path: null, 17 | }) 18 | 19 | test( 20 | `should resolve relative to postcss 'from' option`, 21 | checkFixture, 22 | "resolve-from", 23 | { path: null }, 24 | { from: "test/fixtures/file.css" }, 25 | ) 26 | 27 | test( 28 | `should resolve relative to 'path' which resolved with cwd`, 29 | checkFixture, 30 | "resolve-path-cwd", 31 | { path: "test/fixtures/imports" }, 32 | ) 33 | 34 | test( 35 | `should resolve relative to 'path' which resolved with 'root'`, 36 | checkFixture, 37 | "resolve-path-root", 38 | { root: "test/fixtures", path: "imports" }, 39 | ) 40 | 41 | test("should resolve local modules", checkFixture, "resolve-local-modules", { 42 | path: null, 43 | }) 44 | 45 | test( 46 | "should resolve modules with path option", 47 | checkFixture, 48 | "resolve-path-modules", 49 | { 50 | path: "test/fixtures/imports/modules", 51 | }, 52 | ) 53 | 54 | test( 55 | "should be able to consume npm package or local modules", 56 | checkFixture, 57 | "resolve-modules", 58 | { path: null }, 59 | { from: "test/fixtures/imports/foo.css" }, 60 | ) 61 | 62 | test( 63 | "should be able to consume npm sub packages", 64 | checkFixture, 65 | "resolve-npm-subpackages", 66 | { path: null }, 67 | { from: "test/fixtures/imports/foo.css" }, 68 | ) 69 | 70 | test( 71 | "should be able to consume modules from custom modules directories", 72 | checkFixture, 73 | "resolve-custom-modules", 74 | { path: null, addModulesDirectories: ["shared_modules"] }, 75 | { from: "test/fixtures/imports/foo.css" }, 76 | ) 77 | 78 | test( 79 | "should resolve modules with node subpath imports with a custom resolver", 80 | checkFixture, 81 | "subpath", 82 | { 83 | resolve: (id, basedir) => { 84 | // see: https://nodejs.org/api/packages.html#subpath-imports 85 | if (id.startsWith("#")) { 86 | const pkgJSON = JSON.parse( 87 | fs.readFileSync(path.join(basedir, "package.json")), 88 | ) 89 | 90 | return path.join(basedir, pkgJSON.imports[id]) 91 | } 92 | 93 | return id 94 | }, 95 | path: "test/fixtures/imports/modules", 96 | }, 97 | ) 98 | -------------------------------------------------------------------------------- /test/shared_modules/shared-auto/index.css: -------------------------------------------------------------------------------- 1 | .shared-auto{} 2 | -------------------------------------------------------------------------------- /test/shared_modules/shared-by-hand/style.css: -------------------------------------------------------------------------------- 1 | .shared-byHand{} 2 | -------------------------------------------------------------------------------- /test/shared_modules/shared-dep/index.css: -------------------------------------------------------------------------------- 1 | .shared-dep{} 2 | -------------------------------------------------------------------------------- /test/shared_modules/shared-fake/main.css: -------------------------------------------------------------------------------- 1 | .shared-fake{} 2 | -------------------------------------------------------------------------------- /test/shared_modules/shared-fake/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style": "main.css" 3 | } 4 | -------------------------------------------------------------------------------- /test/shared_modules/shared-nest/index.css: -------------------------------------------------------------------------------- 1 | @import "shared-ed"; 2 | -------------------------------------------------------------------------------- /test/shared_modules/shared-nest/shared_modules/shared-ed/index.css: -------------------------------------------------------------------------------- 1 | .shared-nested{} 2 | -------------------------------------------------------------------------------- /test/shared_modules/shared-use-dep-too/index.css: -------------------------------------------------------------------------------- 1 | @import "shared-dep"; 2 | -------------------------------------------------------------------------------- /test/shared_modules/shared-use-dep/index.css: -------------------------------------------------------------------------------- 1 | @import "shared-dep"; 2 | -------------------------------------------------------------------------------- /test/sourcemap/imported.css: -------------------------------------------------------------------------------- 1 | html { 2 | background: blue; 3 | } 4 | -------------------------------------------------------------------------------- /test/sourcemap/in.css: -------------------------------------------------------------------------------- 1 | @import "imported.css"; 2 | 3 | body { 4 | color: red; 5 | } 6 | -------------------------------------------------------------------------------- /test/sourcemap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Please view source & check sourcemap

-------------------------------------------------------------------------------- /test/sourcemap/out.css: -------------------------------------------------------------------------------- 1 | html { 2 | background: blue; 3 | } 4 | 5 | body { 6 | color: red; 7 | } 8 | 9 | /*# sourceMappingURL=out.css.map */ 10 | -------------------------------------------------------------------------------- /test/sourcemap/out.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["test/sourcemap/imported.css","test/sourcemap/in.css"],"names":[],"mappings":"AAAA;EACE,gBAAgB;AAClB;;ACAA;EACE,UAAU;AACZ","file":"test/sourcemap/in.css","sourcesContent":["html {\n background: blue;\n}\n","@import \"imported.css\";\n\nbody {\n color: red;\n}\n"]} 2 | -------------------------------------------------------------------------------- /test/sourcemap/out.css.win.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["test/sourcemap/imported.css","test/sourcemap/in.css"],"names":[],"mappings":"AAAA;EACE,gBAAgB;AAClB;;ACAA;EACE,UAAU;AACZ","file":"test\\sourcemap\\in.css","sourcesContent":["html {\n background: blue;\n}\n","@import \"imported.css\";\n\nbody {\n color: red;\n}\n"]} 2 | -------------------------------------------------------------------------------- /test/supports-condition.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // external tooling 3 | const test = require("ava") 4 | 5 | // internal tooling 6 | const checkFixture = require("./helpers/check-fixture") 7 | 8 | test( 9 | "should resolve supports conditions of import statements", 10 | checkFixture, 11 | "supports-condition-import", 12 | ) 13 | -------------------------------------------------------------------------------- /test/syntax-error.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | // builtin tooling 3 | const fs = require("fs") 4 | 5 | // external tooling 6 | const test = require("ava") 7 | const postcss = require("postcss") 8 | 9 | // plugin 10 | const atImport = require("..") 11 | 12 | test("SyntaxError in imported file throws", t => { 13 | return postcss(atImport({ path: "test/fixtures/imports" })) 14 | .process(fs.readFileSync("test/fixtures/syntax-error.css", "utf8"), { 15 | from: undefined, 16 | }) 17 | .then(() => t.fail("should error out")) 18 | .catch(err => t.truthy(err)) 19 | }) 20 | -------------------------------------------------------------------------------- /test/web_modules/web-auto/index.css: -------------------------------------------------------------------------------- 1 | .web-auto{} 2 | -------------------------------------------------------------------------------- /test/web_modules/web-by-hand/style.css: -------------------------------------------------------------------------------- 1 | .web-byHand{} 2 | -------------------------------------------------------------------------------- /test/web_modules/web-dep/index.css: -------------------------------------------------------------------------------- 1 | .web-dep{} 2 | -------------------------------------------------------------------------------- /test/web_modules/web-fake/main.css: -------------------------------------------------------------------------------- 1 | .web-fake{} 2 | -------------------------------------------------------------------------------- /test/web_modules/web-fake/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "style": "main.css" 3 | } 4 | -------------------------------------------------------------------------------- /test/web_modules/web-nest/index.css: -------------------------------------------------------------------------------- 1 | @import "web-ed"; 2 | -------------------------------------------------------------------------------- /test/web_modules/web-nest/web_modules/web-ed/index.css: -------------------------------------------------------------------------------- 1 | .web-nested{} 2 | -------------------------------------------------------------------------------- /test/web_modules/web-use-dep-too/index.css: -------------------------------------------------------------------------------- 1 | @import "web-dep"; 2 | -------------------------------------------------------------------------------- /test/web_modules/web-use-dep/index.css: -------------------------------------------------------------------------------- 1 | @import "web-dep"; 2 | --------------------------------------------------------------------------------