From f0efd4a25e5d5206504669d1de0213454f30a177 Mon Sep 17 00:00:00 2001
From: Romain Menke <11521496+romainmenke@users.noreply.github.com>
Date: Thu, 24 Aug 2023 15:33:03 +0200
Subject: [PATCH] `postcss-bundler` (#1090)
---
.github/ISSUE_TEMPLATE/css-issue.yml | 1 +
.github/ISSUE_TEMPLATE/plugin-issue.yml | 1 +
.github/labeler.yml | 3 +
package-lock.json | 49 ++++
plugin-packs/postcss-bundler/.gitignore | 6 +
plugin-packs/postcss-bundler/.nvmrc | 1 +
plugin-packs/postcss-bundler/.tape.mjs | 21 ++
plugin-packs/postcss-bundler/CHANGELOG.md | 47 ++++
plugin-packs/postcss-bundler/INSTALL.md | 235 ++++++++++++++++++
plugin-packs/postcss-bundler/LICENSE.md | 18 ++
plugin-packs/postcss-bundler/README.md | 83 +++++++
plugin-packs/postcss-bundler/dist/index.cjs | 1 +
plugin-packs/postcss-bundler/dist/index.d.ts | 5 +
plugin-packs/postcss-bundler/dist/index.mjs | 1 +
.../dist/postcss-import/index.d.ts | 5 +
.../postcss-import/lib/apply-conditions.d.ts | 3 +
.../dist/postcss-import/lib/apply-styles.d.ts | 3 +
.../dist/postcss-import/lib/conditions.d.ts | 5 +
.../dist/postcss-import/lib/data-url.d.ts | 2 +
.../lib/format-import-prelude.d.ts | 1 +
.../dist/postcss-import/lib/load-content.d.ts | 1 +
.../dist/postcss-import/lib/names.d.ts | 6 +
.../dist/postcss-import/lib/noop-plugin.d.ts | 8 +
.../postcss-import/lib/parse-at-import.d.ts | 7 +
.../postcss-import/lib/parse-statements.d.ts | 4 +
.../dist/postcss-import/lib/parse-styles.d.ts | 4 +
.../dist/postcss-import/lib/resolve-id.d.ts | 2 +
.../dist/postcss-import/lib/statement.d.ts | 34 +++
plugin-packs/postcss-bundler/docs/README.md | 57 +++++
plugin-packs/postcss-bundler/package.json | 89 +++++++
plugin-packs/postcss-bundler/src/index.ts | 20 ++
.../src/postcss-import/index.ts | 30 +++
.../postcss-import/lib/apply-conditions.ts | 127 ++++++++++
.../src/postcss-import/lib/apply-styles.ts | 19 ++
.../src/postcss-import/lib/conditions.ts | 5 +
.../src/postcss-import/lib/data-url.ts | 22 ++
.../lib/format-import-prelude.ts | 26 ++
.../src/postcss-import/lib/load-content.ts | 10 +
.../src/postcss-import/lib/names.ts | 6 +
.../src/postcss-import/lib/noop-plugin.ts | 13 +
.../src/postcss-import/lib/parse-at-import.ts | 164 ++++++++++++
.../postcss-import/lib/parse-statements.ts | 159 ++++++++++++
.../src/postcss-import/lib/parse-styles.ts | 196 +++++++++++++++
.../src/postcss-import/lib/resolve-id.ts | 22 ++
.../src/postcss-import/lib/statement.ts | 56 +++++
plugin-packs/postcss-bundler/test/_import.mjs | 6 +
.../postcss-bundler/test/_require.cjs | 6 +
plugin-packs/postcss-bundler/test/basic.css | 4 +
.../postcss-bundler/test/basic.expect.css | 47 ++++
.../postcss-bundler/test/does-not-exist.css | 1 +
.../test/does-not-exist.expect.css | 1 +
.../postcss-bundler/test/examples/example.css | 2 +
.../test/examples/example.expect.css | 6 +
.../test/examples/imports/basic.css | 3 +
.../postcss-bundler/test/images/green.png | Bin 0 -> 107 bytes
.../postcss-bundler/test/imports/basic.css | 19 ++
.../postcss-bundler/test/imports/green.png | Bin 0 -> 107 bytes
.../test/imports/minified-source.css | 3 +
.../postcss-bundler/test/leading-slash.css | 1 +
.../test/leading-slash.expect.css | 1 +
plugin-packs/postcss-bundler/tsconfig.json | 10 +
rollup/configs/externals.mjs | 4 +
62 files changed, 1692 insertions(+)
create mode 100644 plugin-packs/postcss-bundler/.gitignore
create mode 100644 plugin-packs/postcss-bundler/.nvmrc
create mode 100644 plugin-packs/postcss-bundler/.tape.mjs
create mode 100644 plugin-packs/postcss-bundler/CHANGELOG.md
create mode 100644 plugin-packs/postcss-bundler/INSTALL.md
create mode 100644 plugin-packs/postcss-bundler/LICENSE.md
create mode 100644 plugin-packs/postcss-bundler/README.md
create mode 100644 plugin-packs/postcss-bundler/dist/index.cjs
create mode 100644 plugin-packs/postcss-bundler/dist/index.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/index.mjs
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/index.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/apply-conditions.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/apply-styles.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/conditions.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/data-url.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/format-import-prelude.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/load-content.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/names.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/noop-plugin.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-at-import.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-statements.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-styles.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/resolve-id.d.ts
create mode 100644 plugin-packs/postcss-bundler/dist/postcss-import/lib/statement.d.ts
create mode 100644 plugin-packs/postcss-bundler/docs/README.md
create mode 100644 plugin-packs/postcss-bundler/package.json
create mode 100644 plugin-packs/postcss-bundler/src/index.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/index.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/apply-conditions.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/apply-styles.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/conditions.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/data-url.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/format-import-prelude.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/load-content.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/names.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/noop-plugin.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/parse-at-import.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/parse-statements.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/parse-styles.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/resolve-id.ts
create mode 100644 plugin-packs/postcss-bundler/src/postcss-import/lib/statement.ts
create mode 100644 plugin-packs/postcss-bundler/test/_import.mjs
create mode 100644 plugin-packs/postcss-bundler/test/_require.cjs
create mode 100644 plugin-packs/postcss-bundler/test/basic.css
create mode 100644 plugin-packs/postcss-bundler/test/basic.expect.css
create mode 100644 plugin-packs/postcss-bundler/test/does-not-exist.css
create mode 100644 plugin-packs/postcss-bundler/test/does-not-exist.expect.css
create mode 100644 plugin-packs/postcss-bundler/test/examples/example.css
create mode 100644 plugin-packs/postcss-bundler/test/examples/example.expect.css
create mode 100644 plugin-packs/postcss-bundler/test/examples/imports/basic.css
create mode 100644 plugin-packs/postcss-bundler/test/images/green.png
create mode 100644 plugin-packs/postcss-bundler/test/imports/basic.css
create mode 100644 plugin-packs/postcss-bundler/test/imports/green.png
create mode 100644 plugin-packs/postcss-bundler/test/imports/minified-source.css
create mode 100644 plugin-packs/postcss-bundler/test/leading-slash.css
create mode 100644 plugin-packs/postcss-bundler/test/leading-slash.expect.css
create mode 100644 plugin-packs/postcss-bundler/tsconfig.json
diff --git a/.github/ISSUE_TEMPLATE/css-issue.yml b/.github/ISSUE_TEMPLATE/css-issue.yml
index c1670cf14..6700d9358 100644
--- a/.github/ISSUE_TEMPLATE/css-issue.yml
+++ b/.github/ISSUE_TEMPLATE/css-issue.yml
@@ -59,6 +59,7 @@ body:
multiple: true
options:
- PostCSS Preset Env
+ - PostCSS Bundler
- CSS All Property
- CSS Blank Pseudo
- CSS Has Pseudo
diff --git a/.github/ISSUE_TEMPLATE/plugin-issue.yml b/.github/ISSUE_TEMPLATE/plugin-issue.yml
index 10d29080b..5dc15842c 100644
--- a/.github/ISSUE_TEMPLATE/plugin-issue.yml
+++ b/.github/ISSUE_TEMPLATE/plugin-issue.yml
@@ -61,6 +61,7 @@ body:
multiple: true
options:
- PostCSS Preset Env
+ - PostCSS Bundler
- CSS All Property
- CSS Blank Pseudo
- CSS Has Pseudo
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 299ecd54b..872846e0a 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -17,6 +17,9 @@
"plugin-packs/postcss-preset-env":
- plugin-packs/postcss-preset-env/**
+"plugin-packs/postcss-bundler":
+ - plugin-packs/postcss-bundler/**
+
"cli":
- cli/csstools-cli/**
- packages/base-cli/**
diff --git a/package-lock.json b/package-lock.json
index 0cace916a..17b7fce58 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2057,6 +2057,10 @@
"resolved": "plugins/postcss-base-plugin",
"link": true
},
+ "node_modules/@csstools/postcss-bundler": {
+ "resolved": "plugin-packs/postcss-bundler",
+ "link": true
+ },
"node_modules/@csstools/postcss-cascade-layers": {
"resolved": "plugins/postcss-cascade-layers",
"link": true
@@ -3470,6 +3474,18 @@
"node": ">=12"
}
},
+ "node_modules/@rmenke/css-package-conditional-3": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rmenke/css-package-conditional-3/-/css-package-conditional-3-1.0.0.tgz",
+ "integrity": "sha512-pzscCcvYWHGFtvXwvEq2vCP9mORwuVii2jIT2077nHr6uQlcdr8orvAVRc4WxJMRNNIW14AiomxXNGKzidDu8g==",
+ "dev": true
+ },
+ "node_modules/@rmenke/css-package-main": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@rmenke/css-package-main/-/css-package-main-1.0.0.tgz",
+ "integrity": "sha512-mWJukMz6LzLiMKdN9y1rZtgiY3W9vV6pRLUHWStlLAFYEXAP2mpnueHHcuqNIdd5/hU8sknTk0+Ohx712VQm1A==",
+ "dev": true
+ },
"node_modules/@rmenke/css-tokenizer-tests": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@rmenke/css-tokenizer-tests/-/css-tokenizer-tests-1.1.3.tgz",
@@ -12553,6 +12569,38 @@
"postcss-selector-parser": "^6.0.13"
}
},
+ "plugin-packs/postcss-bundler": {
+ "name": "@csstools/postcss-bundler",
+ "version": "0.1.0-alpha.6",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "dependencies": {
+ "@csstools/css-parser-algorithms": "2.3.1",
+ "@csstools/css-tokenizer": "^2.2.0",
+ "@csstools/postcss-rebase-url": "^0.1.0-alpha.0"
+ },
+ "devDependencies": {
+ "@csstools/postcss-tape": "*",
+ "@rmenke/css-package-conditional-3": "^1.0.0",
+ "@rmenke/css-package-main": "^1.0.0",
+ "open-props": "^1.5.11"
+ },
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4"
+ }
+ },
"plugin-packs/postcss-preset-env": {
"version": "9.1.1",
"funding": [
@@ -14032,6 +14080,7 @@
}
},
"plugins/postcss-rebase-url": {
+ "name": "@csstools/postcss-rebase-url",
"version": "0.1.0-alpha.0",
"funding": [
{
diff --git a/plugin-packs/postcss-bundler/.gitignore b/plugin-packs/postcss-bundler/.gitignore
new file mode 100644
index 000000000..e5b28db4a
--- /dev/null
+++ b/plugin-packs/postcss-bundler/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+package-lock.json
+yarn.lock
+*.result.css
+*.result.css.map
+*.result.html
diff --git a/plugin-packs/postcss-bundler/.nvmrc b/plugin-packs/postcss-bundler/.nvmrc
new file mode 100644
index 000000000..6ed5da955
--- /dev/null
+++ b/plugin-packs/postcss-bundler/.nvmrc
@@ -0,0 +1 @@
+v20.2.0
diff --git a/plugin-packs/postcss-bundler/.tape.mjs b/plugin-packs/postcss-bundler/.tape.mjs
new file mode 100644
index 000000000..40daa3c7f
--- /dev/null
+++ b/plugin-packs/postcss-bundler/.tape.mjs
@@ -0,0 +1,21 @@
+import { postcssTape } from '@csstools/postcss-tape';
+import plugin from '@csstools/postcss-bundler';
+
+const testCases = {
+ basic: {
+ message: "supports basic usage",
+ },
+ 'leading-slash': {
+ message: "does not infer a root to resolve leading slash imports",
+ exception: /Failed to find \'\/imports\/basic.css\'/,
+ },
+ 'does-not-exist': {
+ message: "throws on files that don't exist",
+ exception: /Failed to find 'imports\/does-not-exist.css'/,
+ },
+ 'examples/example': {
+ message: 'minimal example',
+ },
+}
+
+postcssTape(plugin)(testCases);
diff --git a/plugin-packs/postcss-bundler/CHANGELOG.md b/plugin-packs/postcss-bundler/CHANGELOG.md
new file mode 100644
index 000000000..973d1b17a
--- /dev/null
+++ b/plugin-packs/postcss-bundler/CHANGELOG.md
@@ -0,0 +1,47 @@
+# Changes to PostCSS Bundler
+
+### Unreleased (major)
+
+- Initial major version
+
+### 0.1.0-alpha.6
+
+_August 16, 2023_
+
+- Reduce filesystem access
+
+### 0.1.0-alpha.5
+
+_August 15, 2023_
+
+- Stricter parsing algorithm
+
+### 0.1.0-alpha.4
+
+_August 15, 2023_
+
+- Fix sourcemaps
+
+### 0.1.0-alpha.3
+
+_August 14, 2023_
+
+- Fix the path from which node modules are resolved
+
+### 0.1.0-alpha.2
+
+_August 14, 2023_
+
+- Use the `node_modules:` scheme to reference imports from a `node_modules` package
+
+### 0.1.0-alpha.1
+
+_August 14, 2023_
+
+- Use the `npm:` scheme to reference imports from a `node_modules` package
+
+### 0.1.0-alpha.0
+
+_August 14, 2023_
+
+- Initial version
diff --git a/plugin-packs/postcss-bundler/INSTALL.md b/plugin-packs/postcss-bundler/INSTALL.md
new file mode 100644
index 000000000..615d45900
--- /dev/null
+++ b/plugin-packs/postcss-bundler/INSTALL.md
@@ -0,0 +1,235 @@
+# Installing PostCSS Bundler
+
+[PostCSS Bundler] runs in all Node environments, with special instructions for:
+
+- [Node](#node)
+- [PostCSS CLI](#postcss-cli)
+- [PostCSS Load Config](#postcss-load-config)
+- [Webpack](#webpack)
+- [Next.js](#nextjs)
+- [Gulp](#gulp)
+- [Grunt](#grunt)
+
+
+
+## Node
+
+Add [PostCSS Bundler] to your project:
+
+```bash
+npm install postcss @csstools/postcss-bundler --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+// commonjs
+const postcss = require('postcss');
+const postcssBundler = require('@csstools/postcss-bundler');
+
+postcss([
+ postcssBundler(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+```js
+// esm
+import postcss from 'postcss';
+import postcssBundler from '@csstools/postcss-bundler';
+
+postcss([
+ postcssBundler(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+## PostCSS CLI
+
+Add [PostCSS CLI] to your project:
+
+```bash
+npm install postcss-cli @csstools/postcss-bundler --save-dev
+```
+
+Use [PostCSS Bundler] in your `postcss.config.js` configuration file:
+
+```js
+const postcssBundler = require('@csstools/postcss-bundler');
+
+module.exports = {
+ plugins: [
+ postcssBundler(/* pluginOptions */)
+ ]
+}
+```
+
+## PostCSS Load Config
+
+If your framework/CLI supports [`postcss-load-config`](https://github.com/postcss/postcss-load-config).
+
+```bash
+npm install @csstools/postcss-bundler --save-dev
+```
+
+`package.json`:
+
+```json
+{
+ "postcss": {
+ "plugins": {
+ "@csstools/postcss-bundler": {}
+ }
+ }
+}
+```
+
+`.postcssrc.json`:
+
+```json
+{
+ "plugins": {
+ "@csstools/postcss-bundler": {}
+ }
+}
+```
+
+_See the [README of `postcss-load-config`](https://github.com/postcss/postcss-load-config#usage) for more usage options._
+
+## Webpack
+
+_Webpack version 5_
+
+Add [PostCSS Loader] to your project:
+
+```bash
+npm install postcss-loader @csstools/postcss-bundler --save-dev
+```
+
+Use [PostCSS Bundler] in your Webpack configuration:
+
+```js
+module.exports = {
+ module: {
+ rules: [
+ {
+ test: /\.css$/i,
+ use: [
+ "style-loader",
+ {
+ loader: "css-loader",
+ options: { importLoaders: 1 },
+ },
+ {
+ loader: "postcss-loader",
+ options: {
+ postcssOptions: {
+ plugins: [
+ // Other plugins,
+ [
+ "@csstools/postcss-bundler",
+ {
+ // Options
+ },
+ ],
+ ],
+ },
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
+```
+
+## Next.js
+
+Read the instructions on how to [customize the PostCSS configuration in Next.js](https://nextjs.org/docs/advanced-features/customizing-postcss-config)
+
+```bash
+npm install @csstools/postcss-bundler --save-dev
+```
+
+Use [PostCSS Bundler] in your `postcss.config.json` file:
+
+```json
+{
+ "plugins": [
+ "@csstools/postcss-bundler"
+ ]
+}
+```
+
+```json5
+{
+ "plugins": [
+ [
+ "@csstools/postcss-bundler",
+ {
+ // Optionally add plugin options
+ }
+ ]
+ ]
+}
+```
+
+## Gulp
+
+Add [Gulp PostCSS] to your project:
+
+```bash
+npm install gulp-postcss @csstools/postcss-bundler --save-dev
+```
+
+Use [PostCSS Bundler] in your Gulpfile:
+
+```js
+const postcss = require('gulp-postcss');
+const postcssBundler = require('@csstools/postcss-bundler');
+
+gulp.task('css', function () {
+ var plugins = [
+ postcssBundler(/* pluginOptions */)
+ ];
+
+ return gulp.src('./src/*.css')
+ .pipe(postcss(plugins))
+ .pipe(gulp.dest('.'));
+});
+```
+
+## Grunt
+
+Add [Grunt PostCSS] to your project:
+
+```bash
+npm install grunt-postcss @csstools/postcss-bundler --save-dev
+```
+
+Use [PostCSS Bundler] in your Gruntfile:
+
+```js
+const postcssBundler = require('@csstools/postcss-bundler');
+
+grunt.loadNpmTasks('grunt-postcss');
+
+grunt.initConfig({
+ postcss: {
+ options: {
+ processors: [
+ postcssBundler(/* pluginOptions */)
+ ]
+ },
+ dist: {
+ src: '*.css'
+ }
+ }
+});
+```
+
+[Gulp PostCSS]: https://github.com/postcss/gulp-postcss
+[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS CLI]: https://github.com/postcss/postcss-cli
+[PostCSS Loader]: https://github.com/postcss/postcss-loader
+[PostCSS Bundler]: https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-bundler
+[Next.js]: https://nextjs.org
diff --git a/plugin-packs/postcss-bundler/LICENSE.md b/plugin-packs/postcss-bundler/LICENSE.md
new file mode 100644
index 000000000..e8ae93b9f
--- /dev/null
+++ b/plugin-packs/postcss-bundler/LICENSE.md
@@ -0,0 +1,18 @@
+MIT No Attribution (MIT-0)
+
+Copyright © CSSTools Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the “Software”), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/plugin-packs/postcss-bundler/README.md b/plugin-packs/postcss-bundler/README.md
new file mode 100644
index 000000000..a922b50d4
--- /dev/null
+++ b/plugin-packs/postcss-bundler/README.md
@@ -0,0 +1,83 @@
+# PostCSS Bundler [][PostCSS]
+
+[][npm-url] [][cli-url] [][discord]
+
+[PostCSS Bundler] bundles your CSS without changing the way you write CSS.
+
+This plugin pack contains :
+- a bundler based on standard CSS `@import` statements.
+- a rebaser that rewrites URLs in your CSS.
+
+Goal and focus :
+- if your CSS works without bundling it **should** work with [PostCSS Bundler]
+- if your CSS works as a bundle it **must** work without bundling
+
+`examples/example.css` :
+```pcss
+@import url("imports/basic.css");
+@import url("node_modules:open-props/red");
+```
+
+`examples/imports/basic.css`:
+```pcss
+.foo {
+ background: url('../../images/green.png');
+}
+```
+
+when bundled :
+```pcss
+/* imports/basic.css */
+.foo {
+ background: url("../images/green.png");
+}
+/* node_modules:open-props/red */
+:where(html){--red-0:#fff5f5;--red-1:#ffe3e3;--red-2:#ffc9c9;--red-3:#ffa8a8;--red-4:#ff8787;--red-5:#ff6b6b;--red-6:#fa5252;--red-7:#f03e3e;--red-8:#e03131;--red-9:#c92a2a;--red-10:#b02525;--red-11:#962020;--red-12:#7d1a1a}
+```
+
+## Usage
+
+Add [PostCSS Bundler] to your project:
+
+```bash
+npm install postcss @csstools/postcss-bundler --save-dev
+```
+
+Use it as a [PostCSS] plugin:
+
+```js
+const postcss = require('postcss');
+const postcssBundler = require('@csstools/postcss-bundler');
+
+postcss([
+ postcssBundler(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
+```
+
+[PostCSS Bundler] runs in all Node environments, with special
+instructions for:
+
+- [Node](INSTALL.md#node)
+- [PostCSS CLI](INSTALL.md#postcss-cli)
+- [PostCSS Load Config](INSTALL.md#postcss-load-config)
+- [Webpack](INSTALL.md#webpack)
+- [Next.js](INSTALL.md#nextjs)
+- [Gulp](INSTALL.md#gulp)
+- [Grunt](INSTALL.md#grunt)
+
+## `postcss-import`
+
+[`postcss-import`](https://github.com/postcss/postcss-import) is also a CSS bundler and parts of [PostCSS Bundler] are based on it.
+While creating this plugin we also submitted patches to [`postcss-import`](https://github.com/postcss/postcss-import) where possible.
+
+[PostCSS Bundler] is tuned differently and lacks configuration options that are present in [`postcss-import`](https://github.com/postcss/postcss-import).
+
+[PostCSS Bundler] is intended to just work and to be a drop-in replacement for native CSS `@import` statements.
+
+[cli-url]: https://github.com/csstools/postcss-plugins/actions/workflows/test.yml?query=workflow/test
+
+[discord]: https://discord.gg/bUadyRwkJS
+[npm-url]: https://www.npmjs.com/package/@csstools/postcss-bundler
+
+[PostCSS]: https://github.com/postcss/postcss
+[PostCSS Bundler]: https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-bundler
diff --git a/plugin-packs/postcss-bundler/dist/index.cjs b/plugin-packs/postcss-bundler/dist/index.cjs
new file mode 100644
index 000000000..f3a11e940
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/index.cjs
@@ -0,0 +1 @@
+"use strict";var e=require("path"),t=require("@csstools/css-parser-algorithms"),n=require("@csstools/css-tokenizer"),s=require("module"),r=require("fs/promises"),o=require("@csstools/postcss-rebase-url");function isWarning(e){return"warning"===e.type}function isCharsetStatement(e){return"charset"===e.type}function isImportStatement(e){return"import"===e.type}const i=/^data:text\/css(?:;(base64|plain))?,/i,a=/^data:text\/css;base64,/i,p=/^data:text\/css;plain,/i;function isValidDataURL(e){return!!e&&i.test(e)}const l=/^charset$/i,u=/^import$/i,c=/^url$/i,d=/^layer$/i,m=/^supports$/i;function parseAtImport(e){const s=t.parseListOfComponentValues(n.tokenize({css:e}));let r="",o="";const i=[],a=[],p=[];e:for(let e=0;e0||p.length>0)return!1;i.push("");continue}if(t.isFunctionNode(l)&&d.test(l.getName())){if(i.length>0||p.length>0)return!1;i.push(t.stringify([trim(l.value)]));continue}if(t.isFunctionNode(l)&&m.test(l.getName())){if(p.length>0)return!1;p.push(t.stringify([trim(l.value)]));continue}const u=trim(s.slice(e)).flatMap((e=>e.tokens())),f=t.parseCommaSeparatedListOfComponentValues(u).map((e=>t.stringify([trim(e)])));a.push(...f);break}return r=stripHash(r),!!r&&{uri:r,fullUri:o,layer:i,media:a,supports:p}}function trim(e){let n=0,s=e.length;for(let s=0;s=0;n--){const r=e[n];if(!t.isWhitespaceNode(r)&&!t.isCommentNode(r)){s=n+1;break}}return e.slice(n,s)}function stripHash(e){try{const t=new URL(e,"http://example.com");return t.hash?e.slice(0,e.length-t.hash.length):e}catch{return e}}function parseStatements(e,t,n,s,r){const o=[];if("document"===t.type)return t.each((t=>{o.push(...parseStatements(e,t,n,s,r))})),o;let i=[];return t.each((t=>{let a;"atrule"===t.type&&(u.test(t.name)?a=parseImport(e,t,n,s,r):l.test(t.name)&&(a=parseCharset(e,t,n,s,r))),a?(i.length&&(o.push({type:"nodes",nodes:i,conditions:[...s],from:r,importingNode:n}),i=[]),o.push(a)):i.push(t)})),i.length&&o.push({type:"nodes",nodes:i,conditions:[...s],from:r,importingNode:n}),o}function parseCharset(e,t,n,s,r){return t.prev()?e.warn("@charset must precede all other statements",{node:t}):{type:"charset",node:t,conditions:[...s],from:r,importingNode:n}}function parseImport(e,t,n,s,r){let o=t.prev();for(;o;)if("comment"!==o.type){if("atrule"!==o.type||!u.test(o.name))break;o=o.prev()}else o=o.prev();for(;o;)if("comment"!==o.type)if("atrule"===o.type&&l.test(o.name))o=o.prev();else{if("atrule"!==o.type||!d.test(o.name)||o.nodes)return e.warn("@import must precede all other statements (besides @charset or empty @layer)",{node:t});o=o.prev()}else o=o.prev();if(t.nodes)return e.warn("It looks like you didn't end your @import statement correctly. Child nodes are attached to it.",{node:t});const i={type:"import",uri:"",fullUri:"",node:t,conditions:[...s],from:r,importingNode:n},a=parseAtImport(t.params);if(!a)return e.warn(`Invalid @import statement in '${t.toString()}'`,{node:t});const{layer:p,media:c,supports:m,uri:f,fullUri:h}=a;return i.uri=f,i.fullUri=h,(c.length>0||p.length>0||m.length>0)&&i.conditions.push({layer:p,media:c,supports:m}),i}function resolveId(t,n,r){let o="";if(t.startsWith("node_modules:"))try{o=s.createRequire(n).resolve(t.slice(13))}catch(e){throw r.error(`Failed to find '${t}'`)}else o=e.resolve(n,t);return o}async function loadContent(e){return isValidDataURL(e)?(t=e,a.test(t)?Buffer.from(t.slice(21),"base64").toString():p.test(t)?decodeURIComponent(t.slice(20)):decodeURIComponent(t.slice(14))):r.readFile(e,"utf-8");var t}const noopPlugin=()=>({postcssPlugin:"noop-plugin",Once(){}});async function parseStyles(e,t,n,s,r,o){const i=parseStatements(e,t,n,s,r);for(const t of i)isImportStatement(t)&&isProcessableURL(t.uri)&&await resolveImportId(e,t,o);let a=null;const p=[],l=[];function handleCharset(e){if(a){if(e.node.params.toLowerCase()!==a.node.params.toLowerCase()){var t,n;throw e.node.error(`Incompatible @charset statements:\n ${e.node.params} specified in ${null==(t=e.node.source)?void 0:t.input.file}\n ${a.node.params} specified in ${null==(n=a.node.source)?void 0:n.input.file}`)}}else a=e}return i.forEach((e=>{isCharsetStatement(e)?handleCharset(e):isImportStatement(e)?e.children?e.children.forEach((e=>{isImportStatement(e)?p.push(e):isCharsetStatement(e)?handleCharset(e):l.push(e)})):p.push(e):"nodes"===e.type&&l.push(e)})),a?[a,...p.concat(l)]:p.concat(l)}async function resolveImportId(t,n,s){var r;if(isValidDataURL(n.uri))return void(n.children=await loadImportContent(t,n,n.uri,s));if(isValidDataURL(n.from[n.from.length-1]))return n.children=[],void t.warn(`Unable to import '${n.uri}' from a stylesheet that is embedded in a data url`,{node:n.node});const o=n.node;let i;if(null==(r=o.source)||null==(r=r.input)||!r.file)return n.children=[],void t.warn("The current PostCSS AST Node is lacking a source file reference. This is most likely a bug in a PostCSS plugin.",{node:n.node});i=o.source.input.file;const a=e.dirname(i),p=resolveId(n.uri,a,o);t.messages.push({type:"dependency",plugin:"postcss-bundler",resolved:p,parent:i});const l=await loadImportContent(t,n,p,s);n.children=l??[]}async function loadImportContent(e,t,n,s){var r,o;const{conditions:i,from:a,node:p}=t;if(a.includes(n))return;let u;try{u=await loadContent(n)}catch{throw p.error(`Failed to find '${t.uri}'`)}const c=await s([noopPlugin()]).process(u,{from:n,parser:(null==(r=e.opts.syntax)?void 0:r.parse)??e.opts.parser??void 0}),d=c.root;return e.messages=e.messages.concat(c.messages),"atrule"===(null==(o=d.first)?void 0:o.type)&&l.test(d.first.name)?d.first.after(s.comment({text:`${t.uri}`,source:p.source})):d.prepend(s.comment({text:`${t.uri}`,source:p.source})),parseStyles(e,d,p,i,[...a,n],s)}function isProcessableURL(e){if(/^(?:[a-z]+:)?\/\//i.test(e))return!1;try{if(new URL(e,"https://example.com").search)return!1}catch{}return!0}function formatImportPrelude(e,t,n){const s=[];if(e.length){const t=e.join(".");let n="layer";t&&(n=`layer(${t})`),s.push(n)}return 1===n.length?s.push(`supports(${n[0]})`):n.length>0&&s.push(`supports(${n.map((e=>`(${e})`)).join(" and ")})`),t.length&&s.push(t.join(", ")),s.join(" ")}function applyConditions(e,t){e.forEach(((n,s)=>{if(isWarning(n))return;if(!n.conditions.length||isCharsetStatement(n))return;if(isImportStatement(n)){if(1===n.conditions.length)n.node.params=`${n.fullUri} ${formatImportPrelude(n.conditions[0].layer,n.conditions[0].media,n.conditions[0].supports)}`;else if(n.conditions.length>1){const e=n.conditions.slice().reverse(),t=e.pop();let s=`${n.fullUri} ${formatImportPrelude(t.layer,t.media,t.supports)}`;for(const t of e)s=`'data:text/css;base64,${Buffer.from(`@import ${s}`).toString("base64")}' ${formatImportPrelude(t.layer,t.media,t.supports)}`;n.node.params=s}return}const{nodes:r}=n;if(!r.length)return;const{parent:o}=r[0];if(!o)return;const i=[];for(const e of n.conditions){if(e.media.length>0){var a;const s=t({name:"media",params:e.media.join(", "),source:(null==(a=n.importingNode)?void 0:a.source)??o.source});i.push(s)}if(e.supports.length>0){var p;const s=t({name:"supports",params:1===e.supports.length?`(${e.supports[0]})`:e.supports.map((e=>`(${e})`)).join(" and "),source:(null==(p=n.importingNode)?void 0:p.source)??o.source});i.push(s)}if(e.layer.length>0){var l;const s=t({name:"layer",params:e.layer.join("."),source:(null==(l=n.importingNode)?void 0:l.source)??o.source});i.push(s)}}const u=i[0];if(!u)return;for(let e=0;e{e.parent=void 0})),r[0].raws.before=r[0].raws.before||"\n",c.append(r),e[s]={type:"nodes",nodes:[u],conditions:n.conditions,from:n.from,importingNode:n.importingNode}}))}function applyStyles(e,t){t.nodes=[],e.forEach((e=>{isCharsetStatement(e)||isImportStatement(e)?(e.node.parent=void 0,t.append(e.node)):"nodes"===e.type&&e.nodes.forEach((e=>{e.parent=void 0,t.append(e)}))}))}noopPlugin.postcss=!0;const creator$1=()=>({postcssPlugin:"postcss-bundler",async Once(e,{result:t,atRule:n,postcss:s}){const r=await parseStyles(t,e,null,[],[],s);applyConditions(r,n),applyStyles(r,e)}});creator$1.postcss=!0;const creator=()=>({postcssPlugin:"postcss-bundler",plugins:[creator$1(),o()]});creator.postcss=!0,module.exports=creator;
diff --git a/plugin-packs/postcss-bundler/dist/index.d.ts b/plugin-packs/postcss-bundler/dist/index.d.ts
new file mode 100644
index 000000000..4ea42dfa5
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/index.d.ts
@@ -0,0 +1,5 @@
+import type { PluginCreator } from 'postcss';
+/** postcss-bundler plugin options */
+export type pluginOptions = never;
+declare const creator: PluginCreator;
+export default creator;
diff --git a/plugin-packs/postcss-bundler/dist/index.mjs b/plugin-packs/postcss-bundler/dist/index.mjs
new file mode 100644
index 000000000..8f62ff449
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/index.mjs
@@ -0,0 +1 @@
+import e from"path";import{parseListOfComponentValues as t,isWhitespaceNode as n,isCommentNode as r,isTokenNode as o,isFunctionNode as s,stringify as i,parseCommaSeparatedListOfComponentValues as a}from"@csstools/css-parser-algorithms";import{tokenize as l,TokenType as p}from"@csstools/css-tokenizer";import u from"module";import c from"fs/promises";import d from"@csstools/postcss-rebase-url";function isWarning(e){return"warning"===e.type}function isCharsetStatement(e){return"charset"===e.type}function isImportStatement(e){return"import"===e.type}const m=/^data:text\/css(?:;(base64|plain))?,/i,f=/^data:text\/css;base64,/i,h=/^data:text\/css;plain,/i;function isValidDataURL(e){return!!e&&m.test(e)}const g=/^charset$/i,y=/^import$/i,v=/^url$/i,I=/^layer$/i,S=/^supports$/i;function parseAtImport(e){const u=t(l({css:e}));let c="",d="";const m=[],f=[],h=[];e:for(let e=0;e0||h.length>0)return!1;m.push("");continue}if(s(t)&&I.test(t.getName())){if(m.length>0||h.length>0)return!1;m.push(i([trim(t.value)]));continue}if(s(t)&&S.test(t.getName())){if(h.length>0)return!1;h.push(i([trim(t.value)]));continue}const l=trim(u.slice(e)).flatMap((e=>e.tokens())),g=a(l).map((e=>i([trim(e)])));f.push(...g);break}return c=stripHash(c),!!c&&{uri:c,fullUri:d,layer:m,media:f,supports:h}}function trim(e){let t=0,o=e.length;for(let o=0;o=0;t--){const s=e[t];if(!n(s)&&!r(s)){o=t+1;break}}return e.slice(t,o)}function stripHash(e){try{const t=new URL(e,"http://example.com");return t.hash?e.slice(0,e.length-t.hash.length):e}catch{return e}}function parseStatements(e,t,n,r,o){const s=[];if("document"===t.type)return t.each((t=>{s.push(...parseStatements(e,t,n,r,o))})),s;let i=[];return t.each((t=>{let a;"atrule"===t.type&&(y.test(t.name)?a=parseImport(e,t,n,r,o):g.test(t.name)&&(a=parseCharset(e,t,n,r,o))),a?(i.length&&(s.push({type:"nodes",nodes:i,conditions:[...r],from:o,importingNode:n}),i=[]),s.push(a)):i.push(t)})),i.length&&s.push({type:"nodes",nodes:i,conditions:[...r],from:o,importingNode:n}),s}function parseCharset(e,t,n,r,o){return t.prev()?e.warn("@charset must precede all other statements",{node:t}):{type:"charset",node:t,conditions:[...r],from:o,importingNode:n}}function parseImport(e,t,n,r,o){let s=t.prev();for(;s;)if("comment"!==s.type){if("atrule"!==s.type||!y.test(s.name))break;s=s.prev()}else s=s.prev();for(;s;)if("comment"!==s.type)if("atrule"===s.type&&g.test(s.name))s=s.prev();else{if("atrule"!==s.type||!I.test(s.name)||s.nodes)return e.warn("@import must precede all other statements (besides @charset or empty @layer)",{node:t});s=s.prev()}else s=s.prev();if(t.nodes)return e.warn("It looks like you didn't end your @import statement correctly. Child nodes are attached to it.",{node:t});const i={type:"import",uri:"",fullUri:"",node:t,conditions:[...r],from:o,importingNode:n},a=parseAtImport(t.params);if(!a)return e.warn(`Invalid @import statement in '${t.toString()}'`,{node:t});const{layer:l,media:p,supports:u,uri:c,fullUri:d}=a;return i.uri=c,i.fullUri=d,(p.length>0||l.length>0||u.length>0)&&i.conditions.push({layer:l,media:p,supports:u}),i}function resolveId(t,n,r){let o="";if(t.startsWith("node_modules:"))try{o=u.createRequire(n).resolve(t.slice(13))}catch(e){throw r.error(`Failed to find '${t}'`)}else o=e.resolve(n,t);return o}async function loadContent(e){return isValidDataURL(e)?(t=e,f.test(t)?Buffer.from(t.slice(21),"base64").toString():h.test(t)?decodeURIComponent(t.slice(20)):decodeURIComponent(t.slice(14))):c.readFile(e,"utf-8");var t}const noopPlugin=()=>({postcssPlugin:"noop-plugin",Once(){}});async function parseStyles(e,t,n,r,o,s){const i=parseStatements(e,t,n,r,o);for(const t of i)isImportStatement(t)&&isProcessableURL(t.uri)&&await resolveImportId(e,t,s);let a=null;const l=[],p=[];function handleCharset(e){if(a){if(e.node.params.toLowerCase()!==a.node.params.toLowerCase()){var t,n;throw e.node.error(`Incompatible @charset statements:\n ${e.node.params} specified in ${null==(t=e.node.source)?void 0:t.input.file}\n ${a.node.params} specified in ${null==(n=a.node.source)?void 0:n.input.file}`)}}else a=e}return i.forEach((e=>{isCharsetStatement(e)?handleCharset(e):isImportStatement(e)?e.children?e.children.forEach((e=>{isImportStatement(e)?l.push(e):isCharsetStatement(e)?handleCharset(e):p.push(e)})):l.push(e):"nodes"===e.type&&p.push(e)})),a?[a,...l.concat(p)]:l.concat(p)}async function resolveImportId(t,n,r){var o;if(isValidDataURL(n.uri))return void(n.children=await loadImportContent(t,n,n.uri,r));if(isValidDataURL(n.from[n.from.length-1]))return n.children=[],void t.warn(`Unable to import '${n.uri}' from a stylesheet that is embedded in a data url`,{node:n.node});const s=n.node;let i;if(null==(o=s.source)||null==(o=o.input)||!o.file)return n.children=[],void t.warn("The current PostCSS AST Node is lacking a source file reference. This is most likely a bug in a PostCSS plugin.",{node:n.node});i=s.source.input.file;const a=e.dirname(i),l=resolveId(n.uri,a,s);t.messages.push({type:"dependency",plugin:"postcss-bundler",resolved:l,parent:i});const p=await loadImportContent(t,n,l,r);n.children=p??[]}async function loadImportContent(e,t,n,r){var o,s;const{conditions:i,from:a,node:l}=t;if(a.includes(n))return;let p;try{p=await loadContent(n)}catch{throw l.error(`Failed to find '${t.uri}'`)}const u=await r([noopPlugin()]).process(p,{from:n,parser:(null==(o=e.opts.syntax)?void 0:o.parse)??e.opts.parser??void 0}),c=u.root;return e.messages=e.messages.concat(u.messages),"atrule"===(null==(s=c.first)?void 0:s.type)&&g.test(c.first.name)?c.first.after(r.comment({text:`${t.uri}`,source:l.source})):c.prepend(r.comment({text:`${t.uri}`,source:l.source})),parseStyles(e,c,l,i,[...a,n],r)}function isProcessableURL(e){if(/^(?:[a-z]+:)?\/\//i.test(e))return!1;try{if(new URL(e,"https://example.com").search)return!1}catch{}return!0}function formatImportPrelude(e,t,n){const r=[];if(e.length){const t=e.join(".");let n="layer";t&&(n=`layer(${t})`),r.push(n)}return 1===n.length?r.push(`supports(${n[0]})`):n.length>0&&r.push(`supports(${n.map((e=>`(${e})`)).join(" and ")})`),t.length&&r.push(t.join(", ")),r.join(" ")}function applyConditions(e,t){e.forEach(((n,r)=>{if(isWarning(n))return;if(!n.conditions.length||isCharsetStatement(n))return;if(isImportStatement(n)){if(1===n.conditions.length)n.node.params=`${n.fullUri} ${formatImportPrelude(n.conditions[0].layer,n.conditions[0].media,n.conditions[0].supports)}`;else if(n.conditions.length>1){const e=n.conditions.slice().reverse(),t=e.pop();let r=`${n.fullUri} ${formatImportPrelude(t.layer,t.media,t.supports)}`;for(const t of e)r=`'data:text/css;base64,${Buffer.from(`@import ${r}`).toString("base64")}' ${formatImportPrelude(t.layer,t.media,t.supports)}`;n.node.params=r}return}const{nodes:o}=n;if(!o.length)return;const{parent:s}=o[0];if(!s)return;const i=[];for(const e of n.conditions){if(e.media.length>0){var a;const r=t({name:"media",params:e.media.join(", "),source:(null==(a=n.importingNode)?void 0:a.source)??s.source});i.push(r)}if(e.supports.length>0){var l;const r=t({name:"supports",params:1===e.supports.length?`(${e.supports[0]})`:e.supports.map((e=>`(${e})`)).join(" and "),source:(null==(l=n.importingNode)?void 0:l.source)??s.source});i.push(r)}if(e.layer.length>0){var p;const r=t({name:"layer",params:e.layer.join("."),source:(null==(p=n.importingNode)?void 0:p.source)??s.source});i.push(r)}}const u=i[0];if(!u)return;for(let e=0;e{e.parent=void 0})),o[0].raws.before=o[0].raws.before||"\n",c.append(o),e[r]={type:"nodes",nodes:[u],conditions:n.conditions,from:n.from,importingNode:n.importingNode}}))}function applyStyles(e,t){t.nodes=[],e.forEach((e=>{isCharsetStatement(e)||isImportStatement(e)?(e.node.parent=void 0,t.append(e.node)):"nodes"===e.type&&e.nodes.forEach((e=>{e.parent=void 0,t.append(e)}))}))}noopPlugin.postcss=!0;const creator$1=()=>({postcssPlugin:"postcss-bundler",async Once(e,{result:t,atRule:n,postcss:r}){const o=await parseStyles(t,e,null,[],[],r);applyConditions(o,n),applyStyles(o,e)}});creator$1.postcss=!0;const creator=()=>({postcssPlugin:"postcss-bundler",plugins:[creator$1(),d()]});creator.postcss=!0;export{creator as default};
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/index.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/index.d.ts
new file mode 100644
index 000000000..ebde316c0
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/index.d.ts
@@ -0,0 +1,5 @@
+import type { PluginCreator } from 'postcss';
+/** postcss-import plugin options */
+export type pluginOptions = never;
+declare const creator: PluginCreator;
+export default creator;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/apply-conditions.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/apply-conditions.d.ts
new file mode 100644
index 000000000..450069752
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/apply-conditions.d.ts
@@ -0,0 +1,3 @@
+import type { AtRule, AtRuleProps } from 'postcss';
+import { Statement } from './statement';
+export declare function applyConditions(bundle: Array, atRule: (defaults?: AtRuleProps) => AtRule): void;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/apply-styles.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/apply-styles.d.ts
new file mode 100644
index 000000000..b538e915c
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/apply-styles.d.ts
@@ -0,0 +1,3 @@
+import type { Document, Root } from 'postcss';
+import { Statement } from './statement';
+export declare function applyStyles(bundle: Array, styles: Root | Document): void;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/conditions.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/conditions.d.ts
new file mode 100644
index 000000000..4772aadb0
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/conditions.d.ts
@@ -0,0 +1,5 @@
+export type Condition = {
+ layer: Array;
+ media: Array;
+ supports: Array;
+};
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/data-url.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/data-url.d.ts
new file mode 100644
index 000000000..54aa5ba4c
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/data-url.d.ts
@@ -0,0 +1,2 @@
+export declare function isValidDataURL(url?: string): boolean;
+export declare function dataURLContents(url: string): string;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/format-import-prelude.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/format-import-prelude.d.ts
new file mode 100644
index 000000000..5875aad8e
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/format-import-prelude.d.ts
@@ -0,0 +1 @@
+export declare function formatImportPrelude(layer: Array, media: Array, supports: Array): string;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/load-content.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/load-content.d.ts
new file mode 100644
index 000000000..30257103e
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/load-content.d.ts
@@ -0,0 +1 @@
+export declare function loadContent(filename: string): Promise;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/names.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/names.d.ts
new file mode 100644
index 000000000..231220c98
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/names.d.ts
@@ -0,0 +1,6 @@
+export declare const IS_CHARSET: RegExp;
+export declare const IS_IMPORT: RegExp;
+export declare const IS_URL: RegExp;
+export declare const IS_LAYER: RegExp;
+export declare const IS_SUPPORTS: RegExp;
+export declare const IS_MEDIA: RegExp;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/noop-plugin.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/noop-plugin.d.ts
new file mode 100644
index 000000000..44b4ea068
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/noop-plugin.d.ts
@@ -0,0 +1,8 @@
+declare const noopPlugin: {
+ (): {
+ postcssPlugin: string;
+ Once(): void;
+ };
+ postcss: boolean;
+};
+export default noopPlugin;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-at-import.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-at-import.d.ts
new file mode 100644
index 000000000..b6c31fb53
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-at-import.d.ts
@@ -0,0 +1,7 @@
+export declare function parseAtImport(params: string): false | {
+ uri: string;
+ fullUri: string;
+ layer: string[];
+ media: string[];
+ supports: string[];
+};
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-statements.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-statements.d.ts
new file mode 100644
index 000000000..4a2bdb2bd
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-statements.d.ts
@@ -0,0 +1,4 @@
+import type { AtRule, Document, Result, Root } from 'postcss';
+import { Condition } from './conditions';
+import { Statement } from './statement';
+export declare function parseStatements(result: Result, styles: Root | Document, importingNode: AtRule | null, conditions: Array, from: Array): Array;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-styles.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-styles.d.ts
new file mode 100644
index 000000000..86beb9c50
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/parse-styles.d.ts
@@ -0,0 +1,4 @@
+import type { Document, Postcss, Result, Root, AtRule } from 'postcss';
+import { Statement } from './statement';
+import { Condition } from './conditions';
+export declare function parseStyles(result: Result, styles: Root | Document, importingNode: AtRule | null, conditions: Array, from: Array, postcss: Postcss): Promise;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/resolve-id.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/resolve-id.d.ts
new file mode 100644
index 000000000..e40b3d302
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/resolve-id.d.ts
@@ -0,0 +1,2 @@
+import type { Node } from 'postcss';
+export declare function resolveId(id: string, base: string, node: Node): string;
diff --git a/plugin-packs/postcss-bundler/dist/postcss-import/lib/statement.d.ts b/plugin-packs/postcss-bundler/dist/postcss-import/lib/statement.d.ts
new file mode 100644
index 000000000..a5bf2629c
--- /dev/null
+++ b/plugin-packs/postcss-bundler/dist/postcss-import/lib/statement.d.ts
@@ -0,0 +1,34 @@
+import type { AtRule, ChildNode, Warning } from 'postcss';
+import { Condition } from './conditions';
+export type Statement = ImportStatement | CharsetStatement | NodesStatement | Warning;
+export type NodesStatement = {
+ type: string;
+ nodes: Array;
+ conditions: Array;
+ from: Array;
+ parent?: Statement;
+ importingNode: AtRule | null;
+};
+export type CharsetStatement = {
+ type: string;
+ node: AtRule;
+ conditions: Array;
+ from: Array;
+ parent?: Statement;
+ importingNode: AtRule | null;
+};
+export type ImportStatement = {
+ type: string;
+ uri: string;
+ fullUri: string;
+ node: AtRule;
+ conditions: Array;
+ from: Array;
+ parent?: Statement;
+ children?: Array;
+ importingNode: AtRule | null;
+};
+export declare function isWarning(stmt: Statement): stmt is Warning;
+export declare function isNodesStatement(stmt: Statement): stmt is NodesStatement;
+export declare function isCharsetStatement(stmt: Statement): stmt is CharsetStatement;
+export declare function isImportStatement(stmt: Statement): stmt is ImportStatement;
diff --git a/plugin-packs/postcss-bundler/docs/README.md b/plugin-packs/postcss-bundler/docs/README.md
new file mode 100644
index 000000000..bbc49adcb
--- /dev/null
+++ b/plugin-packs/postcss-bundler/docs/README.md
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+[] bundles your CSS without changing the way you write CSS.
+
+This plugin pack contains :
+- a bundler based on standard CSS `@import` statements.
+- a rebaser that rewrites URLs in your CSS.
+
+Goal and focus :
+- if your CSS works without bundling it **should** work with []
+- if your CSS works as a bundle it **must** work without bundling
+
+`examples/example.css` :
+```pcss
+
+```
+
+`examples/imports/basic.css`:
+```pcss
+
+```
+
+when bundled :
+```pcss
+
+```
+
+
+
+
+
+## `postcss-import`
+
+[`postcss-import`](https://github.com/postcss/postcss-import) is also a CSS bundler and parts of [] are based on it.
+While creating this plugin we also submitted patches to [`postcss-import`](https://github.com/postcss/postcss-import) where possible.
+
+[] is tuned differently and lacks configuration options that are present in [`postcss-import`](https://github.com/postcss/postcss-import).
+
+[] is intended to just work and to be a drop-in replacement for native CSS `@import` statements.
+
+
diff --git a/plugin-packs/postcss-bundler/package.json b/plugin-packs/postcss-bundler/package.json
new file mode 100644
index 000000000..5cedb898a
--- /dev/null
+++ b/plugin-packs/postcss-bundler/package.json
@@ -0,0 +1,89 @@
+{
+ "name": "@csstools/postcss-bundler",
+ "description": "Bundle CSS",
+ "version": "0.1.0-alpha.6",
+ "contributors": [
+ {
+ "name": "Antonio Laguna",
+ "email": "antonio@laguna.es",
+ "url": "https://antonio.laguna.es"
+ },
+ {
+ "name": "Romain Menke",
+ "email": "romainmenke@gmail.com"
+ }
+ ],
+ "license": "MIT-0",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "main": "dist/index.cjs",
+ "module": "dist/index.mjs",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.cjs",
+ "default": "./dist/index.mjs"
+ }
+ },
+ "files": [
+ "CHANGELOG.md",
+ "LICENSE.md",
+ "README.md",
+ "dist"
+ ],
+ "dependencies": {
+ "@csstools/css-parser-algorithms": "2.3.1",
+ "@csstools/css-tokenizer": "^2.2.0",
+ "@csstools/postcss-rebase-url": "^0.1.0-alpha.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4"
+ },
+ "devDependencies": {
+ "@csstools/postcss-tape": "*",
+ "@rmenke/css-package-conditional-3": "^1.0.0",
+ "@rmenke/css-package-main": "^1.0.0",
+ "open-props": "^1.5.11"
+ },
+ "scripts": {
+ "build": "rollup -c ../../rollup/default.mjs",
+ "docs": "node ../../.github/bin/generate-docs/install.mjs && node ../../.github/bin/generate-docs/readme.mjs",
+ "lint": "node ../../.github/bin/format-package-json.mjs",
+ "prepublishOnly": "npm run build && npm run test",
+ "test": "node .tape.mjs && node ./test/_import.mjs && node ./test/_require.cjs",
+ "test:rewrite-expects": "REWRITE_EXPECTS=true node .tape.mjs"
+ },
+ "homepage": "https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-bundler#readme",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/csstools/postcss-plugins.git",
+ "directory": "plugins/postcss-bundler"
+ },
+ "bugs": "https://github.com/csstools/postcss-plugins/issues",
+ "keywords": [
+ "bundler",
+ "import",
+ "postcss-plugin",
+ "url"
+ ],
+ "csstools": {
+ "exportName": "postcssBundler",
+ "humanReadableName": "PostCSS Bundler"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ }
+}
diff --git a/plugin-packs/postcss-bundler/src/index.ts b/plugin-packs/postcss-bundler/src/index.ts
new file mode 100644
index 000000000..74fb0c688
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/index.ts
@@ -0,0 +1,20 @@
+import type { PluginCreator } from 'postcss';
+import postcssImport from './postcss-import/index';
+import postcssRebaseURL from '@csstools/postcss-rebase-url';
+
+/** postcss-bundler plugin options */
+export type pluginOptions = never;
+
+const creator: PluginCreator = () => {
+ return {
+ postcssPlugin: 'postcss-bundler',
+ plugins: [
+ postcssImport(),
+ postcssRebaseURL(),
+ ],
+ };
+};
+
+creator.postcss = true;
+
+export default creator;
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/index.ts b/plugin-packs/postcss-bundler/src/postcss-import/index.ts
new file mode 100644
index 000000000..931a09327
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/index.ts
@@ -0,0 +1,30 @@
+import type { PluginCreator } from 'postcss';
+import { parseStyles } from './lib/parse-styles';
+import { applyConditions } from './lib/apply-conditions';
+import { applyStyles } from './lib/apply-styles';
+
+/** postcss-import plugin options */
+export type pluginOptions = never;
+
+const creator: PluginCreator = () => {
+ return {
+ postcssPlugin: 'postcss-bundler',
+ async Once(styles, { result, atRule, postcss }) {
+ const bundle = await parseStyles(
+ result,
+ styles,
+ null,
+ [],
+ [],
+ postcss,
+ );
+
+ applyConditions(bundle, atRule);
+ applyStyles(bundle, styles);
+ },
+ };
+};
+
+creator.postcss = true;
+
+export default creator;
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/apply-conditions.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/apply-conditions.ts
new file mode 100644
index 000000000..697e064bc
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/apply-conditions.ts
@@ -0,0 +1,127 @@
+import type { AtRule, AtRuleProps } from 'postcss';
+import { Statement, isCharsetStatement, isImportStatement, isWarning } from './statement';
+import { formatImportPrelude } from './format-import-prelude';
+
+export function applyConditions(bundle: Array, atRule: (defaults?: AtRuleProps) => AtRule) {
+ bundle.forEach((stmt, index) => {
+ if (isWarning(stmt)) {
+ return;
+ }
+
+ if (!stmt.conditions.length || isCharsetStatement(stmt)) {
+ return;
+ }
+
+ if (isImportStatement(stmt)) {
+ if (stmt.conditions.length === 1) {
+ stmt.node.params = `${stmt.fullUri} ${formatImportPrelude(
+ stmt.conditions[0].layer,
+ stmt.conditions[0].media,
+ stmt.conditions[0].supports,
+ )}`;
+ } else if (stmt.conditions.length > 1) {
+ const reverseConditions = stmt.conditions.slice().reverse();
+ const first = reverseConditions.pop()!;
+ let params = `${stmt.fullUri} ${formatImportPrelude(
+ first.layer,
+ first.media,
+ first.supports,
+ )}`;
+
+ for (const condition of reverseConditions) {
+ params = `'data:text/css;base64,${Buffer.from(
+ `@import ${params}`,
+ ).toString('base64')}' ${formatImportPrelude(
+ condition.layer,
+ condition.media,
+ condition.supports,
+ )}`;
+ }
+
+ stmt.node.params = params;
+ }
+
+ return;
+ }
+
+ const { nodes } = stmt;
+ if (!nodes.length) {
+ return;
+ }
+
+ const { parent } = nodes[0];
+ if (!parent) {
+ return;
+ }
+
+ const atRules = [];
+
+ // Convert conditions to at-rules
+ for (const condition of stmt.conditions) {
+ if (condition.media.length > 0) {
+ const mediaNode = atRule({
+ name: 'media',
+ params: condition.media.join(', '),
+ source: stmt.importingNode?.source ?? parent.source,
+ });
+
+ atRules.push(mediaNode);
+ }
+
+ if (condition.supports.length > 0) {
+ const supportsNode = atRule({
+ name: 'supports',
+ params:
+ condition.supports.length === 1
+ ? `(${condition.supports[0]})`
+ : condition.supports.map(x => `(${x})`).join(' and '),
+ source: stmt.importingNode?.source ?? parent.source,
+ });
+
+ atRules.push(supportsNode);
+ }
+
+ if (condition.layer.length > 0) {
+ const layerNode = atRule({
+ name: 'layer',
+ params: condition.layer.join('.'),
+ source: stmt.importingNode?.source ?? parent.source,
+ });
+
+ atRules.push(layerNode);
+ }
+ }
+
+ // Add nodes to AST
+ const outerAtRule = atRules[0];
+ if (!outerAtRule) {
+ return;
+ }
+
+ for (let i = 0; i < atRules.length - 1; i++) {
+ atRules[i].append(atRules[i + 1]);
+ }
+ const innerAtRule = atRules[atRules.length - 1];
+
+ parent.insertBefore(nodes[0], outerAtRule);
+
+ // remove nodes
+ nodes.forEach(node => {
+ node.parent = undefined;
+ });
+
+ // better output
+ nodes[0].raws.before = nodes[0].raws.before || '\n';
+
+ // wrap new rules with media query and/or layer at rule
+ innerAtRule.append(nodes);
+
+ bundle[index] = {
+ type: 'nodes',
+ nodes: [outerAtRule],
+ conditions: stmt.conditions,
+ from: stmt.from,
+ importingNode: stmt.importingNode,
+ };
+ });
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/apply-styles.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/apply-styles.ts
new file mode 100644
index 000000000..cee15000c
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/apply-styles.ts
@@ -0,0 +1,19 @@
+import type { Document, Root } from 'postcss';
+import { Statement, isCharsetStatement, isImportStatement } from './statement';
+
+export function applyStyles(bundle: Array, styles: Root | Document) {
+ styles.nodes = [];
+
+ // Strip additional statements.
+ bundle.forEach(stmt => {
+ if (isCharsetStatement(stmt) || isImportStatement(stmt)) {
+ stmt.node.parent = undefined;
+ styles.append(stmt.node);
+ } else if (stmt.type === 'nodes') {
+ stmt.nodes.forEach(node => {
+ node.parent = undefined;
+ styles.append(node);
+ });
+ }
+ });
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/conditions.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/conditions.ts
new file mode 100644
index 000000000..e74c5099a
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/conditions.ts
@@ -0,0 +1,5 @@
+export type Condition = {
+ layer: Array
+ media: Array
+ supports: Array
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/data-url.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/data-url.ts
new file mode 100644
index 000000000..987e0661d
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/data-url.ts
@@ -0,0 +1,22 @@
+const anyDataURLRegexp = /^data:text\/css(?:;(base64|plain))?,/i;
+const base64DataURLRegexp = /^data:text\/css;base64,/i;
+const plainDataURLRegexp = /^data:text\/css;plain,/i;
+
+export function isValidDataURL(url?: string): boolean {
+ return !!url && anyDataURLRegexp.test(url);
+}
+
+export function dataURLContents(url: string): string {
+ if (base64DataURLRegexp.test(url)) {
+ // "data:text/css;base64,".length === 21
+ return Buffer.from(url.slice(21), 'base64').toString();
+ }
+
+ if (plainDataURLRegexp.test(url)) {
+ // "data:text/css;plain,".length === 20
+ return decodeURIComponent(url.slice(20));
+ }
+
+ // "data:text/css,".length === 14
+ return decodeURIComponent(url.slice(14));
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/format-import-prelude.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/format-import-prelude.ts
new file mode 100644
index 000000000..18817b44b
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/format-import-prelude.ts
@@ -0,0 +1,26 @@
+export function formatImportPrelude(layer: Array, media: Array, supports: Array): string {
+ const parts = [];
+
+ if (layer.length) {
+ const layerName = layer.join('.');
+
+ let layerParams = 'layer';
+ if (layerName) {
+ layerParams = `layer(${layerName})`;
+ }
+
+ parts.push(layerParams);
+ }
+
+ if (supports.length === 1) {
+ parts.push(`supports(${supports[0]})`);
+ } else if (supports.length > 0) {
+ parts.push(`supports(${supports.map(x => `(${x})`).join(' and ')})`);
+ }
+
+ if (media.length) {
+ parts.push(media.join(', '));
+ }
+
+ return parts.join(' ');
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/load-content.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/load-content.ts
new file mode 100644
index 000000000..234e9013e
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/load-content.ts
@@ -0,0 +1,10 @@
+import fs from 'fs/promises';
+import { dataURLContents, isValidDataURL } from './data-url';
+
+export async function loadContent(filename: string) {
+ if (isValidDataURL(filename)) {
+ return dataURLContents(filename);
+ }
+
+ return fs.readFile(filename, 'utf-8');
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/names.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/names.ts
new file mode 100644
index 000000000..9f9c37016
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/names.ts
@@ -0,0 +1,6 @@
+export const IS_CHARSET = /^charset$/i;
+export const IS_IMPORT = /^import$/i;
+export const IS_URL = /^url$/i;
+export const IS_LAYER = /^layer$/i;
+export const IS_SUPPORTS = /^supports$/i;
+export const IS_MEDIA = /^media$/i;
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/noop-plugin.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/noop-plugin.ts
new file mode 100644
index 000000000..b5845908e
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/noop-plugin.ts
@@ -0,0 +1,13 @@
+// https://github.com/postcss/postcss/issues/1869
+const noopPlugin = () => {
+ return {
+ postcssPlugin: 'noop-plugin',
+ Once() {
+ // do nothing
+ },
+ };
+};
+
+noopPlugin.postcss = true;
+
+export default noopPlugin;
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-at-import.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-at-import.ts
new file mode 100644
index 000000000..15e826eeb
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-at-import.ts
@@ -0,0 +1,164 @@
+import { ComponentValue, isCommentNode, isFunctionNode, isTokenNode, isWhitespaceNode, parseCommaSeparatedListOfComponentValues, parseListOfComponentValues, stringify } from '@csstools/css-parser-algorithms';
+import { TokenType, tokenize } from '@csstools/css-tokenizer';
+import { IS_LAYER, IS_SUPPORTS, IS_URL } from './names';
+
+export function parseAtImport(params: string) {
+ const componentValues = parseListOfComponentValues(
+ tokenize({ css: params }),
+ );
+
+ let uri = '';
+ let fullUri = '';
+ const layer = [];
+ const media = [];
+ const supports = [];
+
+ PARSING_LOOP:
+ for (let i = 0; i < componentValues.length; i++) {
+ const componentValue = componentValues[i];
+ if (isWhitespaceNode(componentValue) || isCommentNode(componentValue)) {
+ continue;
+ }
+
+ if (
+ isTokenNode(componentValue) &&
+ (
+ componentValue.value[0] === TokenType.String ||
+ componentValue.value[0] === TokenType.URL
+ )
+ ) {
+ if (uri) {
+ return false;
+ }
+
+ uri = componentValue.value[4].value;
+ fullUri = componentValue.value[1];
+ continue;
+ }
+
+ if (
+ isFunctionNode(componentValue) &&
+ IS_URL.test(componentValue.getName())
+ ) {
+ if (uri) {
+ return false;
+ }
+
+ for (let j = 0; j < componentValue.value.length; j++) {
+ const childComponentValue = componentValue.value[j];
+ if (isWhitespaceNode(childComponentValue) || isCommentNode(childComponentValue)) {
+ continue;
+ }
+
+ if (
+ isTokenNode(childComponentValue) &&
+ childComponentValue.value[0] === TokenType.String
+ ) {
+ uri = childComponentValue.value[4].value;
+ fullUri = stringify([[componentValue]]);
+ continue PARSING_LOOP;
+ }
+
+ return false;
+ }
+ }
+
+ if (
+ isTokenNode(componentValue) &&
+ componentValue.value[0] === TokenType.Ident &&
+ IS_LAYER.test(componentValue.value[4].value)
+ ) {
+ if (layer.length > 0 || supports.length > 0) {
+ return false;
+ }
+
+ layer.push('');
+ continue;
+ }
+
+ if (
+ isFunctionNode(componentValue) &&
+ IS_LAYER.test(componentValue.getName())
+ ) {
+ if (layer.length > 0 || supports.length > 0) {
+ return false;
+ }
+
+ layer.push(stringify([trim(componentValue.value)]));
+ continue;
+ }
+
+ if (
+ isFunctionNode(componentValue) &&
+ IS_SUPPORTS.test(componentValue.getName())
+ ) {
+ if (supports.length > 0) {
+ return false;
+ }
+
+ supports.push(stringify([trim(componentValue.value)]));
+ continue;
+ }
+
+ const remainder = trim(componentValues.slice(i));
+ const remainderTokens = remainder.flatMap(x => x.tokens());
+ const list = parseCommaSeparatedListOfComponentValues(remainderTokens);
+ const serializedList = list.map((x) => stringify([trim(x)]));
+ media.push(...serializedList);
+ break;
+ }
+
+ uri = stripHash(uri);
+
+ if (!uri) {
+ return false;
+ }
+
+ return {
+ uri,
+ fullUri,
+ layer,
+ media,
+ supports,
+ };
+}
+
+function trim(componentValues: Array) {
+ let start = 0;
+ let end = componentValues.length;
+
+ for (let i = 0; i < componentValues.length; i++) {
+ const componentValue = componentValues[i];
+ if (isWhitespaceNode(componentValue) || isCommentNode(componentValue)) {
+ continue;
+ }
+
+ start = i;
+ break;
+ }
+
+ for (let i = componentValues.length - 1; i >= 0; i--) {
+ const componentValue = componentValues[i];
+ if (isWhitespaceNode(componentValue) || isCommentNode(componentValue)) {
+ continue;
+ }
+
+ end = i + 1;
+ break;
+ }
+
+ return componentValues.slice(start, end);
+}
+
+function stripHash(str: string) {
+ try {
+ const url = new URL(str, 'http://example.com');
+ if (!url.hash) {
+ return str;
+ }
+
+ return str.slice(0, str.length - url.hash.length);
+ } catch {
+ return str;
+ }
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-statements.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-statements.ts
new file mode 100644
index 000000000..5ff21a4e4
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-statements.ts
@@ -0,0 +1,159 @@
+import type { AtRule, ChildNode, Document, Result, Root, Warning } from 'postcss';
+import { Condition } from './conditions';
+import { CharsetStatement, ImportStatement, Statement } from './statement';
+import { IS_CHARSET, IS_IMPORT, IS_LAYER } from './names';
+import { parseAtImport } from './parse-at-import';
+
+export function parseStatements(result: Result, styles: Root | Document, importingNode: AtRule | null, conditions: Array, from: Array): Array {
+ const statements: Array = [];
+
+ if (styles.type === 'document') {
+ styles.each((root) => {
+ statements.push(
+ ...parseStatements(result, root, importingNode, conditions, from),
+ );
+ });
+
+ return statements;
+ }
+
+ let nodes: Array = [];
+
+ styles.each(node => {
+ let stmt;
+ if (node.type === 'atrule') {
+ if (IS_IMPORT.test(node.name)) {
+ stmt = parseImport(result, node, importingNode, conditions, from);
+ } else if (IS_CHARSET.test(node.name)) {
+ stmt = parseCharset(result, node, importingNode, conditions, from);
+ }
+ }
+
+ if (stmt) {
+ if (nodes.length) {
+ statements.push({
+ type: 'nodes',
+ nodes,
+ conditions: [...conditions],
+ from,
+ importingNode,
+ });
+ nodes = [];
+ }
+ statements.push(stmt);
+ } else {
+ nodes.push(node);
+ }
+ });
+
+ if (nodes.length) {
+ statements.push({
+ type: 'nodes',
+ nodes,
+ conditions: [...conditions],
+ from,
+ importingNode,
+ });
+ }
+
+ return statements;
+}
+
+function parseCharset(result: Result, atRule: AtRule, importingNode: AtRule | null, conditions: Array, from: Array): Warning | CharsetStatement {
+ if (atRule.prev()) {
+ return result.warn('@charset must precede all other statements', {
+ node: atRule,
+ });
+ }
+
+ return {
+ type: 'charset',
+ node: atRule,
+ conditions: [...conditions],
+ from,
+ importingNode,
+ };
+}
+
+function parseImport(result: Result, atRule: AtRule, importingNode: AtRule | null, conditions: Array, from: Array): Warning | ImportStatement {
+ let prev = atRule.prev();
+
+ // `@import` statements may follow other `@import` statements.
+ while (prev) {
+ if (prev.type === 'comment') {
+ prev = prev.prev();
+ continue;
+ }
+
+ if (prev.type === 'atrule' && IS_IMPORT.test(prev.name)) {
+ prev = prev.prev();
+ continue;
+ }
+
+ break;
+ }
+
+ // All `@import` statements may be preceded by `@charset` or `@layer` statements.
+ // But the `@import` statements must be consecutive.
+ while (prev) {
+ if (prev.type === 'comment') {
+ prev = prev.prev();
+ continue;
+ }
+
+ if (prev.type === 'atrule' && IS_CHARSET.test(prev.name)) {
+ prev = prev.prev();
+ continue;
+ }
+
+ if (prev.type === 'atrule' && IS_LAYER.test(prev.name) && !prev.nodes) {
+ prev = prev.prev();
+ continue;
+ }
+
+ return result.warn(
+ '@import must precede all other statements (besides @charset or empty @layer)',
+ { node: atRule },
+ );
+ }
+
+ if (atRule.nodes) {
+ return result.warn(
+ 'It looks like you didn\'t end your @import statement correctly. ' +
+ 'Child nodes are attached to it.',
+ { node: atRule },
+ );
+ }
+
+ const stmt = {
+ type: 'import',
+ uri: '',
+ fullUri: '',
+ node: atRule,
+ conditions: [...conditions],
+ from,
+ importingNode,
+ };
+
+ const parsed = parseAtImport(atRule.params);
+ if (!parsed) {
+ return result.warn(`Invalid @import statement in '${atRule.toString()}'`, {
+ node: atRule,
+ });
+ }
+
+ const { layer, media, supports, uri, fullUri } = parsed;
+
+ stmt.uri = uri;
+ stmt.fullUri = fullUri;
+
+ if (media.length > 0 || layer.length > 0 || supports.length > 0) {
+ stmt.conditions.push({
+ layer,
+ media,
+ supports,
+ });
+ }
+
+ return stmt;
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-styles.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-styles.ts
new file mode 100644
index 000000000..2cad50158
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/parse-styles.ts
@@ -0,0 +1,196 @@
+import path from 'path';
+import type { Document, Postcss, Result, Root, AtRule } from 'postcss';
+import { CharsetStatement, ImportStatement, Statement, isCharsetStatement, isImportStatement } from './statement';
+import { Condition } from './conditions';
+import { isValidDataURL } from './data-url';
+import { parseStatements } from './parse-statements';
+import { resolveId } from './resolve-id';
+import { loadContent } from './load-content';
+import noopPlugin from './noop-plugin';
+import { IS_CHARSET } from './names';
+
+export async function parseStyles(
+ result: Result,
+ styles: Root | Document,
+ importingNode: AtRule | null,
+ conditions: Array,
+ from: Array,
+ postcss: Postcss,
+) {
+ const statements = parseStatements(result, styles, importingNode, conditions, from);
+
+ for (const stmt of statements) {
+ if (!isImportStatement(stmt) || !isProcessableURL(stmt.uri)) {
+ continue;
+ }
+
+ await resolveImportId(result, stmt, postcss);
+ }
+
+ let charset: CharsetStatement | null = null;
+ const imports: Array = [];
+ const bundle: Array = [];
+
+ function handleCharset(stmt: CharsetStatement) {
+ if (!charset) {
+ charset = stmt;
+ } else if (
+ stmt.node.params.toLowerCase() !== charset.node.params.toLowerCase()
+ ) {
+ throw stmt.node.error(
+ `Incompatible @charset statements:
+ ${stmt.node.params} specified in ${stmt.node.source?.input.file}
+ ${charset.node.params} specified in ${charset.node.source?.input.file}`,
+ );
+ }
+ }
+
+ // squash statements and their children
+ statements.forEach(stmt => {
+ if (isCharsetStatement(stmt)) {
+ handleCharset(stmt);
+ } else if (isImportStatement(stmt)) {
+ if (stmt.children) {
+ stmt.children.forEach((child) => {
+ if (isImportStatement(child)) {
+ imports.push(child);
+ } else if (isCharsetStatement(child)) {
+ handleCharset(child);
+ } else {
+ bundle.push(child);
+ }
+ });
+ } else {
+ imports.push(stmt);
+ }
+ } else if (stmt.type === 'nodes') {
+ bundle.push(stmt);
+ }
+ });
+
+ return charset ? [charset, ...imports.concat(bundle)] : imports.concat(bundle);
+}
+
+async function resolveImportId(result: Result, stmt: ImportStatement, postcss: Postcss) {
+ if (isValidDataURL(stmt.uri)) {
+ // eslint-disable-next-line require-atomic-updates
+ stmt.children = await loadImportContent(
+ result,
+ stmt,
+ stmt.uri,
+ postcss,
+ );
+
+ return;
+ } else if (isValidDataURL(stmt.from[stmt.from.length - 1])) {
+ // Data urls can't be used as a base url to resolve imports.
+ // Skip inlining and warn.
+ stmt.children = [];
+ result.warn(
+ `Unable to import '${stmt.uri}' from a stylesheet that is embedded in a data url`,
+ {
+ node: stmt.node,
+ },
+ );
+ return;
+ }
+
+ const atRule = stmt.node;
+ let sourceFile: string;
+ if (atRule.source?.input?.file) {
+ sourceFile = atRule.source.input.file;
+ } else {
+ stmt.children = [];
+ result.warn(
+ 'The current PostCSS AST Node is lacking a source file reference. This is most likely a bug in a PostCSS plugin.',
+ {
+ node: stmt.node,
+ },
+ );
+ return;
+ }
+
+ const base = path.dirname(sourceFile);
+ const resolved = resolveId(stmt.uri, base, atRule);
+
+ result.messages.push({
+ type: 'dependency',
+ plugin: 'postcss-bundler',
+ resolved,
+ parent: sourceFile,
+ });
+
+ const importedContent = await loadImportContent(result, stmt, resolved, postcss);
+ stmt.children = importedContent ?? [];
+}
+
+async function loadImportContent(
+ result: Result,
+ stmt: ImportStatement,
+ filename: string,
+ postcss: Postcss,
+) {
+ const { conditions, from, node } = stmt;
+
+ if (from.includes(filename)) {
+ return;
+ }
+
+ let content: string;
+
+ try {
+ content = await loadContent(filename);
+ } catch {
+ throw node.error(
+ `Failed to find '${stmt.uri}'`,
+ );
+ }
+
+ const importedResult = await postcss([noopPlugin()]).process(
+ content,
+ {
+ from: filename,
+ parser: result.opts.syntax?.parse ?? result.opts.parser ?? undefined,
+ },
+ );
+
+ const styles = importedResult.root;
+ result.messages = result.messages.concat(importedResult.messages);
+
+ if (styles.first?.type === 'atrule' && IS_CHARSET.test(styles.first.name)) {
+ styles.first.after(postcss.comment({ text: `${stmt.uri}`, source: node.source }));
+ } else {
+ styles.prepend(postcss.comment({ text: `${stmt.uri}`, source: node.source }));
+ }
+
+ // recursion: import @import from imported file
+ return parseStyles(
+ result,
+ styles,
+ node,
+ conditions,
+ [...from, filename],
+ postcss,
+ );
+}
+
+function isProcessableURL(uri: string): boolean {
+ // skip protocol base uri (protocol://url) or protocol-relative
+ if (/^(?:[a-z]+:)?\/\//i.test(uri)) {
+ return false;
+ }
+
+ // check for fragment or query
+ try {
+ // needs a base to parse properly
+ const url = new URL(uri, 'https://example.com');
+
+ if (url.search) {
+ return false;
+ }
+ } catch {
+ // Ignore
+ }
+
+ return true;
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/resolve-id.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/resolve-id.ts
new file mode 100644
index 000000000..d06d2691f
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/resolve-id.ts
@@ -0,0 +1,22 @@
+import type { Node } from 'postcss';
+import path from 'path';
+import module from 'module';
+
+export function resolveId(id: string, base: string, node: Node): string {
+ let resolvedPath = '';
+ if (id.startsWith('node_modules:')) {
+ try {
+ const require = module.createRequire(base);
+
+ resolvedPath = require.resolve(id.slice(13));
+ } catch (e) {
+ throw node.error(
+ `Failed to find '${id}'`,
+ );
+ }
+ } else {
+ resolvedPath = path.resolve(base, id);
+ }
+
+ return resolvedPath;
+}
diff --git a/plugin-packs/postcss-bundler/src/postcss-import/lib/statement.ts b/plugin-packs/postcss-bundler/src/postcss-import/lib/statement.ts
new file mode 100644
index 000000000..661b69e31
--- /dev/null
+++ b/plugin-packs/postcss-bundler/src/postcss-import/lib/statement.ts
@@ -0,0 +1,56 @@
+import type { AtRule, ChildNode, Warning } from 'postcss';
+import { Condition } from './conditions';
+
+export type Statement = ImportStatement | CharsetStatement | NodesStatement | Warning;
+
+export type NodesStatement = {
+ type: string
+ nodes: Array
+ conditions: Array
+ from: Array
+
+ parent?: Statement
+
+ importingNode: AtRule | null
+}
+
+export type CharsetStatement = {
+ type: string
+ node: AtRule
+ conditions: Array
+ from: Array
+
+ parent?: Statement
+
+ importingNode: AtRule | null
+}
+
+export type ImportStatement = {
+ type: string
+ uri: string
+ fullUri: string
+ node: AtRule
+ conditions: Array
+ from: Array
+
+ parent?: Statement
+ children?: Array
+
+ importingNode: AtRule | null
+}
+
+export function isWarning(stmt: Statement): stmt is Warning {
+ return stmt.type === 'warning';
+}
+
+export function isNodesStatement(stmt: Statement): stmt is NodesStatement {
+ return stmt.type === 'nodes';
+}
+
+export function isCharsetStatement(stmt: Statement): stmt is CharsetStatement {
+ return stmt.type === 'charset';
+}
+
+export function isImportStatement(stmt: Statement): stmt is ImportStatement {
+ return stmt.type === 'import';
+}
diff --git a/plugin-packs/postcss-bundler/test/_import.mjs b/plugin-packs/postcss-bundler/test/_import.mjs
new file mode 100644
index 000000000..142e3aa2e
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/_import.mjs
@@ -0,0 +1,6 @@
+import assert from 'assert';
+import plugin from '@csstools/postcss-bundler';
+plugin();
+
+assert.ok(plugin.postcss, 'should have "postcss flag"');
+assert.equal(typeof plugin, 'function', 'should return a function');
diff --git a/plugin-packs/postcss-bundler/test/_require.cjs b/plugin-packs/postcss-bundler/test/_require.cjs
new file mode 100644
index 000000000..fa79ec287
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/_require.cjs
@@ -0,0 +1,6 @@
+const assert = require('assert');
+const plugin = require('@csstools/postcss-bundler');
+plugin();
+
+assert.ok(plugin.postcss, 'should have "postcss flag"');
+assert.equal(typeof plugin, 'function', 'should return a function');
diff --git a/plugin-packs/postcss-bundler/test/basic.css b/plugin-packs/postcss-bundler/test/basic.css
new file mode 100644
index 000000000..ae04c4128
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/basic.css
@@ -0,0 +1,4 @@
+@import "imports/basic.css";
+@import url(./imports/minified-source.css);
+@import url("node_modules:@rmenke/css-package-main");
+@import url("node_modules:@rmenke/css-package-conditional-3/styles");
diff --git a/plugin-packs/postcss-bundler/test/basic.expect.css b/plugin-packs/postcss-bundler/test/basic.expect.css
new file mode 100644
index 000000000..a8280dfbc
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/basic.expect.css
@@ -0,0 +1,47 @@
+/* imports/basic.css */
+
+.foo {
+ background: url("images/green.png");
+}
+
+.bar {
+ background: url(imports/green.png);
+}
+
+.url-modifier {
+ background: url("images/green.png" something);
+}
+
+.fragment {
+ background: url("images/green.png#foo");
+}
+
+.search {
+ background: url("images/green.png?foo=bar");
+}
+
+/* ./imports/minified-source.css */
+
+.foo { --var: /* a comment */; bar: red }
+
+.foo { color: red }
+
+.foo { color: red }
+
+.foo { color: rgb(0 0 0) }
+
+.foo { color: rgb(0 0 0) }
+
+.foo { color: rgb(0 0 0) }
+
+/* node_modules:@rmenke/css-package-main */
+
+.box {
+ background-color: green;
+}
+
+/* node_modules:@rmenke/css-package-conditional-3/styles */
+
+.box {
+ background-color: green;
+}
diff --git a/plugin-packs/postcss-bundler/test/does-not-exist.css b/plugin-packs/postcss-bundler/test/does-not-exist.css
new file mode 100644
index 000000000..5a533d25f
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/does-not-exist.css
@@ -0,0 +1 @@
+@import "imports/does-not-exist.css";
diff --git a/plugin-packs/postcss-bundler/test/does-not-exist.expect.css b/plugin-packs/postcss-bundler/test/does-not-exist.expect.css
new file mode 100644
index 000000000..5a533d25f
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/does-not-exist.expect.css
@@ -0,0 +1 @@
+@import "imports/does-not-exist.css";
diff --git a/plugin-packs/postcss-bundler/test/examples/example.css b/plugin-packs/postcss-bundler/test/examples/example.css
new file mode 100644
index 000000000..cc036edc6
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/examples/example.css
@@ -0,0 +1,2 @@
+@import url("imports/basic.css");
+@import url("node_modules:open-props/red");
diff --git a/plugin-packs/postcss-bundler/test/examples/example.expect.css b/plugin-packs/postcss-bundler/test/examples/example.expect.css
new file mode 100644
index 000000000..089a7b932
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/examples/example.expect.css
@@ -0,0 +1,6 @@
+/* imports/basic.css */
+.foo {
+ background: url("../images/green.png");
+}
+/* node_modules:open-props/red */
+:where(html){--red-0:#fff5f5;--red-1:#ffe3e3;--red-2:#ffc9c9;--red-3:#ffa8a8;--red-4:#ff8787;--red-5:#ff6b6b;--red-6:#fa5252;--red-7:#f03e3e;--red-8:#e03131;--red-9:#c92a2a;--red-10:#b02525;--red-11:#962020;--red-12:#7d1a1a}
diff --git a/plugin-packs/postcss-bundler/test/examples/imports/basic.css b/plugin-packs/postcss-bundler/test/examples/imports/basic.css
new file mode 100644
index 000000000..4504bf94c
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/examples/imports/basic.css
@@ -0,0 +1,3 @@
+.foo {
+ background: url('../../images/green.png');
+}
diff --git a/plugin-packs/postcss-bundler/test/images/green.png b/plugin-packs/postcss-bundler/test/images/green.png
new file mode 100644
index 0000000000000000000000000000000000000000..3171cfbaff7e291406850547f61e7ed77614d24e
GIT binary patch
literal 107
zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZGe%~h$gzMu%0P-az$e6&p@Ct}&!rQATxCxe
t$B>G+w+9UwfxJTszn5=2;+Fto!vUY+G6qK9&zW{05l>e?mvv4FO#rrO7&QO@
literal 0
HcmV?d00001
diff --git a/plugin-packs/postcss-bundler/test/imports/basic.css b/plugin-packs/postcss-bundler/test/imports/basic.css
new file mode 100644
index 000000000..bad1b2da9
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/imports/basic.css
@@ -0,0 +1,19 @@
+.foo {
+ background: url('../images/green.png');
+}
+
+.bar {
+ background: url(green.png);
+}
+
+.url-modifier {
+ background: url("../images/green.png" something);
+}
+
+.fragment {
+ background: url("../images/green.png#foo");
+}
+
+.search {
+ background: url("../images/green.png?foo=bar");
+}
diff --git a/plugin-packs/postcss-bundler/test/imports/green.png b/plugin-packs/postcss-bundler/test/imports/green.png
new file mode 100644
index 0000000000000000000000000000000000000000..3171cfbaff7e291406850547f61e7ed77614d24e
GIT binary patch
literal 107
zcmeAS@N?(olHy`uVBq!ia0y~yV6*{ZGe%~h$gzMu%0P-az$e6&p@Ct}&!rQATxCxe
t$B>G+w+9UwfxJTszn5=2;+Fto!vUY+G6qK9&zW{05l>e?mvv4FO#rrO7&QO@
literal 0
HcmV?d00001
diff --git a/plugin-packs/postcss-bundler/test/imports/minified-source.css b/plugin-packs/postcss-bundler/test/imports/minified-source.css
new file mode 100644
index 000000000..1902d5de4
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/imports/minified-source.css
@@ -0,0 +1,3 @@
+.foo { --var: /* a comment */; bar: red } .foo { color: red } .foo { color: red } .foo { color: rgb(0 0 0) } .foo { color: rgb(0 0 0) } .foo { color: rgb(0 0 0) }
+
+/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImJhc2ljLmNzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUNFLHNCQUFzQixFQUFFLFNBQzFCLEVBRUEsT0FDQyxXQUNELEVBRUEsT0FDQyxXQUNELEVBRUEsT0FDQyxrQkFDRCxFQUVBLE9BQ0Msa0JBQ0QsRUFFQSxPQUNDLGtCQUNEIiwiZmlsZSI6ImJhc2ljLm1pbi5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIuZm9vIHtcbiAgLS12YXI6IC8qIGEgY29tbWVudCAqLzsgYmFyOiByZWQ7XG59XG5cbi5mb28ge1xuXHRjb2xvcjogcmVkO1xufVxuXG4uZm9vIHtcblx0Y29sb3I6ICByZWQ7XG59XG5cbi5mb28ge1xuXHRjb2xvcjogcmdiKDAgIDAgMCk7XG59XG5cbi5mb28ge1xuXHRjb2xvcjogcmdiKDAgLyogYSBjb21tZW50ICovIDAgMCk7XG59XG5cbi5mb28ge1xuXHRjb2xvcjogcmdiKDAgLyogYSBjb21tZW50ICovIC8qIG1vcmUgY29tbWVudHMgKi8gMCAwKTtcbn1cbiJdfQ== */
diff --git a/plugin-packs/postcss-bundler/test/leading-slash.css b/plugin-packs/postcss-bundler/test/leading-slash.css
new file mode 100644
index 000000000..39784dcb6
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/leading-slash.css
@@ -0,0 +1 @@
+@import "/imports/basic.css";
diff --git a/plugin-packs/postcss-bundler/test/leading-slash.expect.css b/plugin-packs/postcss-bundler/test/leading-slash.expect.css
new file mode 100644
index 000000000..39784dcb6
--- /dev/null
+++ b/plugin-packs/postcss-bundler/test/leading-slash.expect.css
@@ -0,0 +1 @@
+@import "/imports/basic.css";
diff --git a/plugin-packs/postcss-bundler/tsconfig.json b/plugin-packs/postcss-bundler/tsconfig.json
new file mode 100644
index 000000000..500af6d26
--- /dev/null
+++ b/plugin-packs/postcss-bundler/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "declarationDir": ".",
+ "strict": true
+ },
+ "include": ["./src/**/*"],
+ "exclude": ["dist"]
+}
diff --git a/rollup/configs/externals.mjs b/rollup/configs/externals.mjs
index 2cc7ff1fe..c2441dc9e 100644
--- a/rollup/configs/externals.mjs
+++ b/rollup/configs/externals.mjs
@@ -1,5 +1,6 @@
export const externalsForCLI = [
'fs',
+ 'fs/promises',
'https',
'path',
'url',
@@ -35,6 +36,7 @@ export const externalsForCLI = [
'@csstools/postcss-normalize-display-values',
'@csstools/postcss-oklab-function',
'@csstools/postcss-progressive-custom-properties',
+ '@csstools/postcss-rebase-url',
'@csstools/postcss-relative-color-syntax',
'@csstools/postcss-scope-pseudo-class',
'@csstools/postcss-stepped-value-functions',
@@ -85,6 +87,7 @@ export const externalsForCLI = [
export const externalsForPlugin = [
'assert',
'fs',
+ 'fs/promises',
'https',
'module',
'path',
@@ -120,6 +123,7 @@ export const externalsForPlugin = [
'@csstools/postcss-normalize-display-values',
'@csstools/postcss-oklab-function',
'@csstools/postcss-progressive-custom-properties',
+ '@csstools/postcss-rebase-url',
'@csstools/postcss-relative-color-syntax',
'@csstools/postcss-scope-pseudo-class',
'@csstools/postcss-stepped-value-functions',