Repository: ProjectEvergreen/greenwood (js)

Issue:
bare CSS `@import` specifiers are not resolving and bundling correctly
<!--
# 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

Observed as part of testing for #1309 that when we try and leverage Spectrum's CSS files through an `@import`, e.g.
```css
/* src/styles/theme.css */
@import url('../../node_modules/@spectrum-web-components/styles/all-large-dark.css');
```

This file will load
![Screenshot 2024-12-02 at 5 05 27 PM](https://github.com/user-attachments/assets/420a2ac6-c0bc-46ac-a71d-aebc4952916a)

BUT, it will lead to errors on other files it references
![Screenshot 2024-12-02 at 5 05 14 PM](https://github.com/user-attachments/assets/c3a1aa64-abaf-42ca-ba12-52643ef1c6a3)


## Details

In theory I thought this was covered by https://github.com/ProjectEvergreen/greenwood/issues/1257 but I guess we are not recursively handling `@import` statements within `@import` statements?  Here's what the file looks like from unpkg
https://unpkg.com/browse/@spectrum-web-components/styles@1.0.2/all-large-dark.css

Perhaps this is specific to `@import` in _node_modules_ specifically though?  Perhaps we should do something similar to #684 and prefix things as such?
```css
/* src/styles/theme.css */
@import url('~@spectrum-web-components/styles/all-large-dark.css');
```

Or check if things do / don't have a `./` or `../` and automatically resolve that way?

Pull request description:
bug/issue 1338 handle bundling of bare CSS `@import` specifiers
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue

resolves #1338 

## Documentation 

N / A

## Summary of Changes

1. Resolve and bundle bare CSS `@import` specifiers
1. Add test case

## TODO
1. [x] should probably rename url variables for better naming for better clarity (e.g. `url` -> `sourceUrl`)
1. [x] rebase after #1341 
1. [x] remove demo code
1. [ ] (nice to have or make a standalone issue) wondering if we could use this technique to handle this case for `url(...)` - https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/src/plugins/resource/plugin-standard-css.js#L75
    - will make an issue once I put together a minimal reproduction for the issue

Patch:
diff --git a/.eslintignore b/.eslintignore
index 216d536e..9a7f8253 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -3,6 +3,7 @@
 **/node_modules/**
 !.eslintrc.cjs
 !.mocharc.js
+packages/**/test/cases/**/adapter-output/**
 packages/init/src/template/**
 packages/init/test/cases/**/output/**
 packages/init/test/cases/**/my-app/**
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 5202ca86..679588e8 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -61,6 +61,7 @@
     "@shoelace-style/shoelace": "^2.18.0",
     "@spectrum-css/card": "^9.3.0",
     "@spectrum-web-components/action-menu": "^1.0.1",
+    "@spectrum-web-components/styles": "^1.0.1",
     "@uswds/web-components": "^0.0.1-alpha",
     "geist": "^1.2.0",
     "lit": "^3.1.0",
diff --git a/packages/cli/src/plugins/resource/plugin-standard-css.js b/packages/cli/src/plugins/resource/plugin-standard-css.js
index 4246c08e..43fa5289 100644
--- a/packages/cli/src/plugins/resource/plugin-standard-css.js
+++ b/packages/cli/src/plugins/resource/plugin-standard-css.js
@@ -11,7 +11,7 @@ import { ResourceInterface } from '../../lib/resource-interface.js';
 import { hashString } from '../../lib/hashing-utils.js';
 import { getResolvedHrefFromPathnameShortcut } from '../../lib/node-modules-utils.js';
 
-function bundleCss(body, url, compilation) {
+function bundleCss(body, sourceUrl, compilation, workingUrl) {
   const { projectDirectory, outputDir, userWorkspace } = compilation.context;
   const ast = parse(body, {
     onParseError(error) {
@@ -27,13 +27,23 @@ function bundleCss(body, url, compilation) {
       if ((type === 'String' || type === 'Url') && this.atrulePrelude && this.atrule.name === 'import') {
         const { value } = node;
 
-        if (value.indexOf('.') === 0 || value.indexOf('/node_modules') === 0) {
-          const resolvedUrl = value.startsWith('/node_modules')
-            ? new URL(getResolvedHrefFromPathnameShortcut(value, projectDirectory))
-            : new URL(value, url);
-          const importContents = fs.readFileSync(resolvedUrl, 'utf-8');
+        if (!value.startsWith('http')) {
+          if (value.indexOf('.') === 0 || value.indexOf('/node_modules') === 0) {
+            const resolvedUrl = value.startsWith('/node_modules')
+              ? new URL(getResolvedHrefFromPathnameShortcut(value, projectDirectory))
+              : new URL(value, sourceUrl);
 
-          optimizedCss += bundleCss(importContents, url, compilation);
+            const importContents = fs.readFileSync(resolvedUrl, 'utf-8');
+
+            optimizedCss += bundleCss(importContents, sourceUrl, compilation, resolvedUrl);
+          } else if (workingUrl) {
+            const resolvedUrl = new URL(`./${value}`, workingUrl);
+            const importContents = fs.readFileSync(resolvedUrl, 'utf-8');
+
+            optimizedCss += bundleCss(importContents, workingUrl, compilation);
+          } else {
+            console.warn(`Unable to resolve ${value} from file => ${sourceUrl}`);
+          }
         } else {
           optimizedCss += `@import url('${value}');`;
         }


Test patch:
diff --git a/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js b/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js
index a1240f0c..e9bf7058 100644
--- a/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js
+++ b/packages/cli/test/cases/build.default.import-node-modules/build.default.import-node-modules.spec.js
@@ -18,8 +18,11 @@
  *     index.html
  *   scripts/
  *     main.js
+ *   styles/
+ *     theme.css
  */
 import chai from 'chai';
+import fs from 'fs';
 import glob from 'glob-promise';
 import { JSDOM } from 'jsdom';
 import path from 'path';
@@ -119,6 +122,25 @@ describe('Build Greenwood With: ', function() {
         expect(await glob.promise(path.join(this.context.publicDir, 'prism-tomorrow.*.css'))).to.have.lengthOf(1);
       });
     });
+
+    describe('<link rel="stylesheet" href="..."> with reference to node_modules with bare @import paths in the <head> tag', function() {
+      it('should have one <link href="..."> tag in the <head> tag', function() {
+        const linkTags = dom.window.document.querySelectorAll('head > link[rel="stylesheet"]');
+        const themeLinkTag = Array.prototype.slice.call(linkTags).filter(link => {
+          return (/theme.*.css/).test(link.href);
+        });
+
+        expect(themeLinkTag.length).to.be.equal(1);
+      });
+
+      it('should have the expected theme.css file in the output directory with the expected content', async function() {
+        const themeFile = await glob.promise(path.join(this.context.publicDir, 'styles/theme.*.css'));
+        const contents = fs.readFileSync(themeFile[0], 'utf-8');
+
+        expect(themeFile).to.have.lengthOf(1);
+        expect(contents.indexOf(':root,:host{--spectrum-global-animation-linear:cubic-bezier(0, 0, 1, 1);')).to.equal(0);
+      });
+    });
   });
 
   after(function() {
diff --git a/packages/cli/test/cases/build.default.import-node-modules/src/pages/index.html b/packages/cli/test/cases/build.default.import-node-modules/src/pages/index.html
index 9d79257c..59f0347e 100644
--- a/packages/cli/test/cases/build.default.import-node-modules/src/pages/index.html
+++ b/packages/cli/test/cases/build.default.import-node-modules/src/pages/index.html
@@ -8,6 +8,7 @@
     </script>
     <script type="module" src="/node_modules/lit-html/lit-html.js"></script>
     <link rel="stylesheet" href="/node_modules/prismjs/themes/prism-tomorrow.css"></link>
+    <link rel="stylesheet" href="../styles/theme.css"></link>
   </head>
 
 </html>
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.default.import-node-modules/src/styles/theme.css b/packages/cli/test/cases/build.default.import-node-modules/src/styles/theme.css
new file mode 100644
index 00000000..13ce1b4c
--- /dev/null
+++ b/packages/cli/test/cases/build.default.import-node-modules/src/styles/theme.css
@@ -0,0 +1,1 @@
+@import url('/node_modules/@spectrum-web-components/styles/all-large-dark.css');
\ No newline at end of file