From 55a6dd263f2dd8e2f965a85880746590af4779d8 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Thu, 10 Oct 2024 15:15:55 -0400 Subject: [PATCH] basic TS support and caveats for other formats --- packages/plugin-css-modules/README.md | 1 + packages/plugin-css-modules/package.json | 3 +- packages/plugin-css-modules/src/index.js | 17 ++++++--- .../greenwood.config.js | 35 +++++++++++++++++++ .../loaders-build.prerender.spec.js | 6 ++-- .../src/components/header/header.js | 2 +- .../src/components/logo/{logo.js => logo.ts} | 2 ++ 7 files changed, 56 insertions(+), 10 deletions(-) rename packages/plugin-css-modules/test/cases/loaders-build.prerender/src/components/logo/{logo.js => logo.ts} (88%) diff --git a/packages/plugin-css-modules/README.md b/packages/plugin-css-modules/README.md index 541971594..8cabc12ae 100644 --- a/packages/plugin-css-modules/README.md +++ b/packages/plugin-css-modules/README.md @@ -100,6 +100,7 @@ From there, Greenwood will scope your CSS by prefixing with the filename and a h There are some caveats to consider when using this plugin: +1. This plugin only supports usage of CSS Modules within vanilla JavaScript, or [TypeScript (_.ts_)](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-typescript) and [JSX (_.jsx_)](https://github.com/ProjectEvergreen/greenwood/tree/master/packages/plugin-import-jsx) when combined with our plugins 1. This plugin only checks for [lower camelCase](https://github.com/css-modules/css-modules/blob/master/docs/naming.md) based class names ```css /* works ✅ */ diff --git a/packages/plugin-css-modules/package.json b/packages/plugin-css-modules/package.json index b06961541..583086dfc 100644 --- a/packages/plugin-css-modules/package.json +++ b/packages/plugin-css-modules/package.json @@ -29,7 +29,8 @@ "acorn-import-attributes": "^1.9.5", "acorn-walk": "^8.0.0", "css-tree": "^3.0.0", - "node-html-parser": "^1.2.21" + "node-html-parser": "^1.2.21", + "sucrase": "^3.35.0" }, "devDependencies": { "@greenwood/cli": "^0.30.0-alpha.6" diff --git a/packages/plugin-css-modules/src/index.js b/packages/plugin-css-modules/src/index.js index 09c0fc121..f8925323b 100644 --- a/packages/plugin-css-modules/src/index.js +++ b/packages/plugin-css-modules/src/index.js @@ -11,6 +11,7 @@ import * as acornWalk from 'acorn-walk'; import * as acorn from 'acorn'; import { hashString } from '@greenwood/cli/src/lib/hashing-utils.js'; import { importAttributes } from 'acorn-import-attributes'; +import { transform } from 'sucrase'; const MODULES_MAP_FILENAME = '__css-modules-map.json'; /* @@ -33,10 +34,14 @@ function getCssModulesMap(compilation) { function walkAllImportsForCssModules(scriptUrl, sheets, compilation) { const scriptContents = fs.readFileSync(scriptUrl, 'utf-8'); + const result = transform(scriptContents, { + transforms: ['typescript', 'jsx'], + jsxRuntime: 'preserve' + }); acornWalk.simple( - acorn.Parser.extend(importAttributes).parse(scriptContents, { - ecmaVersion: '2020', + acorn.Parser.extend(importAttributes).parse(result.code, { + ecmaVersion: 'latest', sourceType: 'module' }), { @@ -67,6 +72,7 @@ function walkAllImportsForCssModules(scriptUrl, sheets, compilation) { // drill down from a SelectorList to its first Selector // and check its first child to see if it is a ClassSelector // and if so, hash that initial class selector + if (node.type === 'SelectorList') { if (node.children?.head?.data?.type === 'Selector') { if (node.children?.head?.data?.children?.head?.data?.type === 'ClassSelector') { @@ -117,8 +123,9 @@ function walkAllImportsForCssModules(scriptUrl, sheets, compilation) { } }) ); - } else if (node.source.value.endsWith('.js')) { - // TODO this will be an issue with say TypeScript... + } else if (value.endsWith('.js') || value.endsWith('.jsx') || value.endsWith('.ts')) { + // no good way to get at async plugin processing so right now + // we can only support what we can provide to acorn const recursiveScriptUrl = new URL(value, scriptUrl); if (fs.existsSync(recursiveScriptUrl)) { @@ -240,7 +247,7 @@ class StripCssModulesResource extends ResourceInterface { acornWalk.simple( acorn.Parser.extend(importAttributes).parse(contents, { - ecmaVersion: '2020', + ecmaVersion: 'latest', sourceType: 'module' }), { diff --git a/packages/plugin-css-modules/test/cases/loaders-build.prerender/greenwood.config.js b/packages/plugin-css-modules/test/cases/loaders-build.prerender/greenwood.config.js index 2bcaf77a5..fce9810f6 100644 --- a/packages/plugin-css-modules/test/cases/loaders-build.prerender/greenwood.config.js +++ b/packages/plugin-css-modules/test/cases/loaders-build.prerender/greenwood.config.js @@ -1,8 +1,43 @@ +import fs from 'fs/promises'; import { greenwoodPluginCssModules } from '../../../src/index.js'; +import { transform } from 'sucrase'; + +class NaiveTsResource { + constructor(compilation, options) { + this.compilation = compilation; + this.options = options; + + this.extensions = ['ts']; + this.contentType = 'text/javascript'; + } + + async shouldServe(url) { + return url.pathname.split('.').pop() === this.extensions[0]; + } + + async serve(url) { + const scriptContents = await fs.readFile(url, 'utf-8'); + const result = transform(scriptContents, { + transforms: ['typescript', 'jsx'], + jsxRuntime: 'preserve' + }); + + return new Response(result.code, { + headers: new Headers({ + 'Content-Type': this.contentType + }) + }); + } +} export default { prerender: true, plugins: [ + { + type: 'resource', + name: 'plugin-naive-ts', + provider: (compilation, options) => new NaiveTsResource(compilation, options) + }, greenwoodPluginCssModules() ] }; \ No newline at end of file diff --git a/packages/plugin-css-modules/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js b/packages/plugin-css-modules/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js index 337fa8e52..cbd791d9c 100644 --- a/packages/plugin-css-modules/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js +++ b/packages/plugin-css-modules/test/cases/loaders-build.prerender/loaders-build.prerender.spec.js @@ -1,6 +1,6 @@ /* * Use Case - * Run Greenwood build with CSS Modules plugin and pre-rendering. + * Run Greenwood build with CSS Modules plugin and pre-rendering, including an example using TypeScript. * * User Result * Should generate a Greenwood project with CSS Modules properly transformed. @@ -28,7 +28,7 @@ * header.js * header.module.css * logo/ - * logo.js + * logo.ts * logo.module.css * index.html */ @@ -253,7 +253,7 @@ describe('Build Greenwood With: ', function() { expect(styleText).to.contain(expectedFooterCss.replace(/\[placeholder\]/g, scopedHash)); }); - it('should have the source CSS class names as scoped class names inlined in a