diff --git a/demo/App.tsx b/demo/App.tsx index e937e817..5d3b97ee 100644 --- a/demo/App.tsx +++ b/demo/App.tsx @@ -38,6 +38,7 @@ function App() { This text is styled by global configured SASS

This text is styled by imported SASS

+

This text is styled by imported SASS using a shared mixin and variable

This text is styled by Tailwind CSS

diff --git a/demo/__tests__/transform.test.tsx b/demo/__tests__/transform.test.tsx index f0e63ee8..92bf2aeb 100644 --- a/demo/__tests__/transform.test.tsx +++ b/demo/__tests__/transform.test.tsx @@ -7,7 +7,7 @@ describe('transform', () => { render(); // TODO: To refactor this test. It frequently need to be updated. expect(document.body.outerHTML).toMatchInlineSnapshot( - `"
\\"logo\\"
\\"logo2\\"

Hello Vite + React!

Less: Blue + Dynamic size (break point 400px)

Less: Yellow

Styled by SCSS Modules

This text is styled by styled-components

This text is styled by global css which is not imported to App.tsx

This text is styled by CSS Modules

This text is styled by global configured SASS

This text is styled by imported SASS

This text is styled by Tailwind CSS

Styled by Emotion

Styled by Stiches

This text is styled by SASS from load paths

An animated element style using @use ~

An animated element style using import ~

Watch me fade in!

Edit App.tsx and save to test HMR updates.

Learn React | Vite Docs

"`, + `"
\\"logo\\"
\\"logo2\\"

Hello Vite + React!

Less: Blue + Dynamic size (break point 400px)

Less: Yellow

Styled by SCSS Modules

This text is styled by styled-components

This text is styled by global css which is not imported to App.tsx

This text is styled by CSS Modules

This text is styled by global configured SASS

This text is styled by imported SASS

This text is styled by imported SASS using a shared mixin and variable

This text is styled by Tailwind CSS

Styled by Emotion

Styled by Stiches

This text is styled by SASS from load paths

An animated element style using @use ~

An animated element style using import ~

Watch me fade in!

Edit App.tsx and save to test HMR updates.

Learn React | Vite Docs

"`, ); }); }); diff --git a/demo/assets/_scss/sharedSassResources/color-vars.scss b/demo/assets/_scss/sharedSassResources/color-vars.scss new file mode 100644 index 00000000..2930a004 --- /dev/null +++ b/demo/assets/_scss/sharedSassResources/color-vars.scss @@ -0,0 +1 @@ +$limeGreen: #32cd32; diff --git a/demo/assets/_scss/sharedSassResources/mixins.scss b/demo/assets/_scss/sharedSassResources/mixins.scss new file mode 100644 index 00000000..7911fa62 --- /dev/null +++ b/demo/assets/_scss/sharedSassResources/mixins.scss @@ -0,0 +1,5 @@ +@mixin desktop { + @media only screen and (min-width: 900px) { + @content; + } +} diff --git a/demo/assets/_scss/style.scss b/demo/assets/_scss/style.scss index 0db3cdaa..83089606 100644 --- a/demo/assets/_scss/style.scss +++ b/demo/assets/_scss/style.scss @@ -8,4 +8,12 @@ header { .imported-sass { color: pink; } + + .imported-sass-shared-var { + color: purple; + + @include desktop { + color: $limeGreen; + } + } } diff --git a/demo/setupTests.js b/demo/setupTests.js index f1bb8d7e..e243a404 100644 --- a/demo/setupTests.js +++ b/demo/setupTests.js @@ -7,6 +7,10 @@ jestPreviewConfigure({ publicFolder: 'demo/public', autoPreview: true, sassLoadPaths: ['demo/assets/_scss/loadPathsExample'], + sharedSassResources: [ + 'demo/assets/_scss/sharedSassResources/color-vars.scss', + 'demo/assets/_scss/sharedSassResources/mixins.scss', + ], }); window.matchMedia = (query) => ({ diff --git a/examples/angular/src/app/app.component.spec.ts b/examples/angular/src/app/app.component.spec.ts index a3d563fe..f74cc381 100644 --- a/examples/angular/src/app/app.component.spec.ts +++ b/examples/angular/src/app/app.component.spec.ts @@ -7,7 +7,7 @@ styleUrls.forEach((styleUrl) => import(styleUrl)); describe('App', () => { it('should work as expected', async () => { const user = userEvent.setup(); - await render(AppComponent); + const { detectChanges } = await render(AppComponent); await user.click(screen.getByTestId('increase')); await user.click(screen.getByTestId('increase')); @@ -16,6 +16,8 @@ describe('App', () => { await user.click(screen.getByTestId('increase')); await user.click(screen.getByTestId('increase')); + detectChanges(); + // Open http://localhost:3336 to see preview // Require to run `jest-preview` server before preview.debug(); diff --git a/examples/angular/src/app/counter/counter.component.spec.ts b/examples/angular/src/app/counter/counter.component.spec.ts index 7b3acc87..b95b7f85 100644 --- a/examples/angular/src/app/counter/counter.component.spec.ts +++ b/examples/angular/src/app/counter/counter.component.spec.ts @@ -1,13 +1,13 @@ import { render, screen } from '@testing-library/angular'; import userEvent from '@testing-library/user-event'; -// import preview from 'jest-preview'; +import preview from 'jest-preview'; import { CounterComponent } from './counter.component'; import './counter.component.css'; describe(CounterComponent.name, () => { it('should work as expected', async () => { const user = userEvent.setup(); - await render(CounterComponent); + const { detectChanges } = await render(CounterComponent); await user.click(screen.getByTestId('increase')); await user.click(screen.getByTestId('increase')); @@ -16,9 +16,11 @@ describe(CounterComponent.name, () => { await user.click(screen.getByTestId('increase')); await user.click(screen.getByTestId('increase')); + detectChanges(); + // Open http://localhost:3336 to see preview // Require to run `jest-preview` server before - // preview.debug(); + preview.debug(); expect(screen.getByTestId('count')).toContainHTML('6'); }); diff --git a/src/configure.ts b/src/configure.ts index ee89a66b..82088610 100644 --- a/src/configure.ts +++ b/src/configure.ts @@ -1,7 +1,7 @@ import path from 'path'; import fs from 'fs'; import chalk from 'chalk'; -import { CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG } from './constants'; +import { CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG, SASS_SHARED_RESOURCES_CONCAT } from './constants'; import { createCacheFolderIfNeeded } from './utils'; import { debug } from './preview'; @@ -13,6 +13,7 @@ interface JestPreviewConfigOptions { autoPreview?: boolean; publicFolder?: string; sassLoadPaths?: string[]; + sharedSassResources?: string[]; } export function jestPreviewConfigure( @@ -21,6 +22,7 @@ export function jestPreviewConfigure( autoPreview = false, publicFolder, sassLoadPaths, + sharedSassResources, }: JestPreviewConfigOptions = { autoPreview: false, sassLoadPaths: [], @@ -72,6 +74,24 @@ export function jestPreviewConfigure( }, ); } + + if (sharedSassResources && sharedSassResources.length > 0) { + createCacheFolderIfNeeded(); + + const resourceFileContents = sharedSassResources.map((sassResourceFile) => { + const filenameComment = `// ${sassResourceFile}`; + const fileContents = fs.readFileSync(sassResourceFile, "utf-8"); + + return `${filenameComment}\n${fileContents}`; + }); + + const resourceFileContentsConcat = resourceFileContents.join('\n\n'); + + fs.writeFileSync( + path.join(CACHE_FOLDER, SASS_SHARED_RESOURCES_CONCAT), + resourceFileContentsConcat, + ); + } } // Omit only, skip, todo, concurrent, each. Couldn't use Omit. Just redeclare for simplicity diff --git a/src/constants.ts b/src/constants.ts index dae98cb0..558ee81a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,2 +1,4 @@ -export const CACHE_FOLDER = './node_modules/.cache/jest-preview'; +export const CACHE_SUB_FOLDER = '.cache/jest-preview'; +export const CACHE_FOLDER = `./node_modules/${CACHE_SUB_FOLDER}`; export const SASS_LOAD_PATHS_CONFIG = 'cache-sass-load-paths.config'; +export const SASS_SHARED_RESOURCES_CONCAT = '_sass-resources-concat.scss'; diff --git a/src/transform.ts b/src/transform.ts index aaecf6dc..9dca4a8b 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -6,7 +6,7 @@ import { pathToFileURL } from 'url'; import camelcase from 'camelcase'; import slash from 'slash'; import { transform } from '@svgr/core'; -import { CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG } from './constants'; +import { CACHE_FOLDER, CACHE_SUB_FOLDER, SASS_LOAD_PATHS_CONFIG, SASS_SHARED_RESOURCES_CONCAT } from './constants'; import { createCacheFolderIfNeeded } from './utils'; // https://github.com/vitejs/vite/blob/c29613013ca1c6d9c77b97e2253ed1f07e40a544/packages/vite/src/node/plugins/css.ts#L97-L98 @@ -397,6 +397,25 @@ function processSass(filename: string): string { sassLoadPathsConfig = []; } + // Workaround for ?bug? with sass.compileString that the location of the source file is not automatically included in load paths + sassLoadPathsConfig.push(path.parse(filename).dir); + + const sharedSassResourcesPath = path.join(CACHE_FOLDER, SASS_SHARED_RESOURCES_CONCAT); + + let sassPrefixedWithSharedResources = ''; + if (fs.existsSync(sharedSassResourcesPath)) { + // Transform the filename for use in "use" statement + const sharedResourcesFilenameForUse = SASS_SHARED_RESOURCES_CONCAT.replace(/^_|\.scss$/g, ''); + // Prepend a "use" statement so shared sass resources are accessible from all source files + const useSharedStatement = `@use '~${CACHE_SUB_FOLDER}/${sharedResourcesFilenameForUse}' as *;`; + const sassContent = fs.readFileSync(filename, 'utf8'); + sassPrefixedWithSharedResources = `${useSharedStatement}\n\n${sassContent}`; + } + + return buildCssResult(sass, sassLoadPathsConfig, sassPrefixedWithSharedResources, filename); +} + +function buildCssResult(sass: any, sassLoadPathsConfig: string[], sassPrefixedWithSharedResources: string, filename: string): string { let cssResult; // An importer that redirects relative URLs starting with "~" to `node_modules` @@ -412,17 +431,25 @@ function processSass(filename: string): string { ); }; - if (sass.compile) { - cssResult = sass.compile(filename, { - loadPaths: sassLoadPathsConfig, - importers: [ - { - findFileUrl(url: string) { - return tildeImporter(url); - }, + if (sassPrefixedWithSharedResources && !sass.compileString) { + console.warn("WARNING: Config specifies sharedSassResources, but your version of Sass doesn't support it - consider updating to v1.45.0 or higher."); + } + + const compileOptions = { + loadPaths: sassLoadPathsConfig, + importers: [ + { + findFileUrl(url: string) { + return tildeImporter(url); }, - ], - }).css; + }, + ], + }; + + if (sassPrefixedWithSharedResources && sass.compileString) { + cssResult = sass.compileString(sassPrefixedWithSharedResources, compileOptions).css; + } else if (sass.compile) { + cssResult = sass.compile(filename, compileOptions).css; } // Because sass.compile is only introduced since sass version 1.45.0 // For older versions, we have to use the legacy API: renderSync diff --git a/website/docs/api/jestPreviewConfigure.md b/website/docs/api/jestPreviewConfigure.md index 40bcb106..a4017841 100644 --- a/website/docs/api/jestPreviewConfigure.md +++ b/website/docs/api/jestPreviewConfigure.md @@ -36,6 +36,12 @@ jestPreviewConfigure({ }); ``` +## sharedSassResources: string[] + +Default: `undefined` + +Optional list of paths to SASS files that define shared resources (e.g. variables, mixins, etc). The paths are relative to the root of the project. Requires SASS v1.45.0 or higher. + ## publicFolder: string Default: `undefined`.