Repository: ProjectEvergreen/greenwood (js)

Issue:
PostCSS plugin breaks bundling of SSR CSS-based import attributes
<!--
# PLEASE READ FIRST
If asking a question, looking for help, or you have an idea for Greenwood, please consider opening a discussion first or chatting with us in our Discord.
-->

## Type of Change

Bug

## Summary

Based on observations in [this project](https://github.com/thescientist13/HP_Stuff/pull/1) it looks like if you are using CSS based Import Attributes and SSR / custom resource loaders, PostCSS is trying to bundle JavaScript based resources and throwing an error
```js
Error [RollupError]: Expression expected
    at getRollupError (file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/rollup@4.28.1/node_modules/rollup/dist/es/shared/parseAst.js:396:41)
    at convertProgram (file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/rollup@4.28.1/node_modules/rollup/dist/es/shared/parseAst.js:1084:26)
    at Object.parseAst [as parse] (file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/rollup@4.28.1/node_modules/rollup/dist/es/shared/parseAst.js:2069:87)
    at Object.transform (file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/@greenwood+cli@0.31.0-alpha.1/node_modules/@greenwood/cli/src/config/rollup.config.js:290:24)
    at async transform (file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/rollup@4.28.1/node_modules/rollup/dist/es/shared/node-entry.js:19663:16)
    at async ModuleLoader.addModuleSource (file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/rollup@4.28.1/node_modules/rollup/dist/es/shared/node-entry.js:19879:36) {
  code: 'PLUGIN_ERROR',
  pos: 627,
  pluginCode: 'PARSE_ERROR',
  plugin: 'greenwood-import-meta-url',
  hook: 'transform',
  id: '/Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/@spectrum-css+card@9.3.0_@spectrum-css+icon@7.2.0_@spectrum-css+tokens@14.6.0__@spectrum-css+_ice4y2gdhp2yq7ivdbp3yo6byi/node_modules/@spectrum-css/card/dist/index.css'
}
```

## Details

I think the issue can be reproduced by having an SSR route that uses (a component) with an import attribute and using `prerender` with [custom resource loaders](https://greenwoodjs.dev/docs/pages/server-rendering/#custom-imports), e.g.
```js
// src/pages/index.js
import sheet from '../styles/theme.css' with { type: 'css' };
```

> I notice that we don't have any tests for this use case for the PostCSS loader
> https://github.com/ProjectEvergreen/greenwood/tree/v0.30.2/packages/plugin-postcss/test/cases

Pull request description:
bug/issue 1355 handle CSS import attributes with PostCSS plugin when prerendering
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue

resolves #1355 

## Documentation 

N / A

## Summary of Changes

1. Don't extension check in PostCSS plugin intercepting lifestyle per - https://github.com/ProjectEvergreen/www.greenwoodjs.dev/pull/153/files#diff-745fdda9fef5a58b2eebf6f4a4ccfffa2506fcfb7c2722295528c375bfe71681R432
1. Have PostCSS plugin forward incoming `response.headers`

## TODO
1. [x] logo FOUC now? - just needed to refine the implementation
1. [x] Downstream testing
1. [x] other instances of extension check in intercepting
    - ~~CSS Modules~~ - N / A
    - Babel - #1366 
1. [x] (own issue) should the contents / results be inlined or externalized (currently being inlined)?  - https://github.com/ProjectEvergreen/greenwood/issues/1365
    - Seems like it should be externalized but we only check for local files - https://github.com/ProjectEvergreen/greenwood/blob/v0.31.0-alpha.1/packages/cli/src/config/rollup.config.js#L34
    - should we also be doing more mergeResponse in _rollup.config.js_?  Had to formally pass `response.headers` here from PostCSS plugin, but maybe this was intentional for this specific use case?
1. [x] split off Babel plugin work into its own PR - #1366 

Patch:
diff --git a/packages/plugin-postcss/package.json b/packages/plugin-postcss/package.json
index fa8a94c8..252601b1 100644
--- a/packages/plugin-postcss/package.json
+++ b/packages/plugin-postcss/package.json
@@ -32,6 +32,7 @@
     "postcss-preset-env": "^7.0.1"
   },
   "devDependencies": {
-    "@greenwood/cli": "^0.31.0-alpha.1"
+    "@greenwood/cli": "^0.31.0-alpha.1",
+    "@spectrum-css/typography": "^6.2.0"
   }
 }
diff --git a/packages/plugin-postcss/src/index.js b/packages/plugin-postcss/src/index.js
index 25e9bc24..05bde0f0 100644
--- a/packages/plugin-postcss/src/index.js
+++ b/packages/plugin-postcss/src/index.js
@@ -35,7 +35,6 @@ class PostCssResource extends ResourceInterface {
 
   async shouldPreIntercept(url, request, response) {
     return url.protocol === 'file:'
-      && url.pathname.split('.').pop() === this.extensions[0]
       && (request?.headers?.get('Content-Type')?.includes('text/css') || response?.headers?.get('Content-Type')?.includes('text/css'));
   }
 
@@ -47,7 +46,11 @@ class PostCssResource extends ResourceInterface {
       ? (await postcss(plugins).process(body, { from: normalizePathnameForWindows(url) })).css
       : body;
 
-    return new Response(css);
+    // preserve original headers (content type / accept)
+    // since this could be used in JS or CSS contexts
+    return new Response(css, {
+      headers: response.headers
+    });
   }
 }
 
diff --git a/yarn.lock b/yarn.lock
index b3e81457..356ab385 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4179,6 +4179,11 @@
   resolved "https://registry.yarnpkg.com/@spectrum-css/card/-/card-9.3.0.tgz#736d6035f75b34e49e9fcb0198ba4667d024ebf9"
   integrity sha512-uMWwh/kkkcOSf8W5Kz1TyQjaWxKeTKm1Gm5XaAyHu+IHk+mYJPrDeZNoYfyIqZ+x/RgyVcCzx2H3VXA/uUaaeA==
 
+"@spectrum-css/typography@^6.2.0":
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-6.2.0.tgz#2bf928d9fb7fb51bc67feeb3d8a82f8ee093ee87"
+  integrity sha512-yaqc4wFY3asUhh3zzdqRI5dVJow7xJ2nL2bo4AADDLT305Dtje5PYO89m+f3nTVf2utKE3H8nlJbWUG57QpFqw==
+
 "@spectrum-web-components/action-button@^1.0.1":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@spectrum-web-components/action-button/-/action-button-1.0.1.tgz#98fd0af1d825ec5c40a54f2d1cc1d311815a169e"


Test patch:
diff --git a/packages/plugin-postcss/test/cases/loaders-default.import-attributes/greenwood.config.js b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/greenwood.config.js
new file mode 100644
index 00000000..8180684f
--- /dev/null
+++ b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/greenwood.config.js
@@ -0,0 +1,8 @@
+import { greenwoodPluginPostCss } from '../../../src/index.js';
+
+export default {
+  prerender: true,
+  plugins: [
+    greenwoodPluginPostCss()
+  ]
+};
\ No newline at end of file
diff --git a/packages/plugin-postcss/test/cases/loaders-default.import-attributes/loaders-default.import-attributes.spec.js b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/loaders-default.import-attributes.spec.js
new file mode 100644
index 00000000..2f1562e8
--- /dev/null
+++ b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/loaders-default.import-attributes.spec.js
@@ -0,0 +1,89 @@
+/*
+ * Use Case
+ * Run Greenwood with default PostCSS config when using Import Attributes.
+ *
+ * User Result
+ * Should generate a bare bones Greenwood build with the user's CSS file correctly minified.
+ *
+ * User Command
+ * greenwood build
+ *
+ * User Config
+ * const pluginPostCss = require('@greenwood/plugin-postcss');
+ *
+ * {
+ *   plugins: [
+ *     pluginPostCss()
+ *  ]
+ * }
+ *
+ * User Workspace
+ *  src/
+ *   components/
+ *     header/
+ *       header.js
+ *       header.css
+ *   pages/
+ *     index.html
+ *   styles/
+ *     theme.css
+ */
+import chai from 'chai';
+import fs from 'fs';
+import glob from 'glob-promise';
+import path from 'path';
+import { runSmokeTest } from '../../../../../test/smoke-test.js';
+import { getOutputTeardownFiles } from '../../../../../test/utils.js';
+import { Runner } from 'gallinago';
+import { fileURLToPath, URL } from 'url';
+
+const expect = chai.expect;
+
+describe('Build Greenwood With: ', function() {
+  const LABEL = 'Default PostCSS configuration and CSS Import Attributes';
+  const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js');
+  const outputPath = fileURLToPath(new URL('.', import.meta.url));
+  let runner;
+
+  before(function() {
+    this.context = {
+      publicDir: path.join(outputPath, 'public')
+    };
+    runner = new Runner(false, true);
+  });
+
+  describe(LABEL, function() {
+
+    before(function() {
+      runner.setup(outputPath);
+      runner.runCommand(cliPath, 'build');
+    });
+
+    runSmokeTest(['public', 'index'], LABEL);
+
+    describe('Page referencing external nested CSS file', function() {
+      it('should output correctly processed nested CSS as non nested', function() {
+        const expectedCss = 'body{color:red}h1{color:blue}';
+        const cssFiles = glob.sync(path.join(this.context.publicDir, 'styles', '*.css'));
+        const css = fs.readFileSync(cssFiles[0], 'utf-8');
+
+        expect(cssFiles.length).to.equal(1);
+        expect(css).to.equal(expectedCss);
+      });
+
+      it('should output correctly processed import attributes in header.js bundle output', function() {
+        const headerFiles = glob.sync(path.join(this.context.publicDir, 'header.*.js'));
+        const js = fs.readFileSync(headerFiles[0], 'utf-8');
+
+        expect(headerFiles.length).to.equal(1);
+        expect(js.indexOf('import e from"/styles/theme.1126086472.css"with{type:"css"};') >= 0).to.equal(true);
+        expect(js.indexOf('import t from"/header.CaS9Xrom.css"with{type:"css"};') >= 0).to.equal(true);
+        expect(js.indexOf('const s=new CSSStyleSheet;s.replaceSync(\'.spectrum{--spectrum-font-family-ar:myriad-arabic') >= 0).to.equal(true);
+      });
+    });
+  });
+
+  after(function() {
+    runner.teardown(getOutputTeardownFiles(outputPath));
+  });
+});
\ No newline at end of file
diff --git a/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/components/header/header.css b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/components/header/header.css
new file mode 100644
index 00000000..eadfef75
--- /dev/null
+++ b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/components/header/header.css
@@ -0,0 +1,3 @@
+header {
+  background-color: aqua;
+}
\ No newline at end of file
diff --git a/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/components/header/header.js b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/components/header/header.js
new file mode 100644
index 00000000..56720653
--- /dev/null
+++ b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/components/header/header.js
@@ -0,0 +1,24 @@
+import themeSheet from '../../styles/theme.css' with { type: 'css' };
+import headerSheet from './header.css' with { type: 'css' };
+import SpectrumTypography from '@spectrum-css/typography' with { type: 'css' };
+
+export default class Header extends HTMLElement {
+  connectedCallback() {
+    if (!this.shadowRoot) {
+      const template = document.createElement('template');
+
+      template.innerHTML = `
+        <header>
+          <span>Welcome to my site</span>
+        </header>
+      `;
+
+      this.attachShadow({ mode: 'open' });
+      this.shadowRoot.appendChild(template.content.cloneNode(true));
+    }
+
+    this.shadowRoot.adoptedStyleSheets = [themeSheet, headerSheet, SpectrumTypography];
+  }
+}
+
+customElements.define('app-header', Header);
\ No newline at end of file
diff --git a/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/pages/index.html b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/pages/index.html
new file mode 100644
index 00000000..c627b7dc
--- /dev/null
+++ b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/pages/index.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en" prefix="og:http://ogp.me/ns#">
+
+  <head>
+    <link rel="stylesheet" href="/styles/theme.css"></link>
+    <script type="module" src="../components/header/header.js"></script>
+  </head>
+
+  <body>
+    <app-header></app-header>
+    <h1>Hello World!</h1>
+  </body>
+  
+</html>
\ No newline at end of file
diff --git a/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/styles/theme.css b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/styles/theme.css
new file mode 100644
index 00000000..9ff1eeec
--- /dev/null
+++ b/packages/plugin-postcss/test/cases/loaders-default.import-attributes/src/styles/theme.css
@@ -0,0 +1,7 @@
+body {
+  color: red;
+}
+
+h1 {
+  color: blue;
+}
\ No newline at end of file