diff --git a/CHANGELOG.md b/CHANGELOG.md index a5b61fa1..3d9e1705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# 0.2.4 + +## Fixes + +- Sass enhancement + # 0.2.3 ## Fixes diff --git a/config/jest/setupTests.js b/config/jest/setupTests.js index 543acf21..8e50f555 100644 --- a/config/jest/setupTests.js +++ b/config/jest/setupTests.js @@ -5,6 +5,7 @@ jestPreviewConfigure({ externalCss: ['demo/global.css', 'demo/assets/_scss/global-style.scss'], publicFolder: 'demo/public', autoPreview: true, + sassLoadPaths: ['demo/assets/_scss/loadPathsExample'], }); window.matchMedia = (query) => ({ diff --git a/demo/App.tsx b/demo/App.tsx index d6112c6a..0c0e81ce 100644 --- a/demo/App.tsx +++ b/demo/App.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; import { css } from '@emotion/react'; import emotionStyled from '@emotion/styled'; import { styled as stichesStyled } from '@stitches/react'; +import '~animate.css/animate.css'; import logo2 from './assets/images/logo.svg'; import './App.css'; @@ -46,6 +47,16 @@ function App() { {/* Reference: https://developer.chrome.com/blog/css-in-js/ */} {/* https://wicg.github.io/construct-stylesheets/ */} <StichesP>Styled by Stiches</StichesP> + <p className="load-path-sass"> + This text is styled by SASS from load paths + </p> + <p className="animate__animated animate__bounce"> + An animated element style using @use ~ + </p> + <div className="animated fadeIn"> + <p>An animated element style using import ~</p> + <p>Watch me fade in!</p> + </div> <p> <button data-testid="increase" diff --git a/demo/__tests__/transform.test.tsx b/demo/__tests__/transform.test.tsx index dea7b636..cf7a64be 100644 --- a/demo/__tests__/transform.test.tsx +++ b/demo/__tests__/transform.test.tsx @@ -6,7 +6,7 @@ describe('transform', () => { it('should generate snapshots correctly in different OS', () => { render(<App />); expect(document.body.outerHTML).toMatchInlineSnapshot( - `"<body><div><div class=\\"App\\"><header class=\\"App-header\\"><img src=\\"/logo.svg\\" class=\\"App-logo\\" alt=\\"logo\\"><img src=\\"/demo/assets/images/logo.svg\\" class=\\"logo2\\" alt=\\"logo2\\"><p>Hello Vite + React!</p><p class=\\"sc-bczRLJ dgihId\\">This text is styled by styled-components</p><p class=\\"global-css\\">This text is styled by global css which is not imported to App.tsx</p><p class=\\"_cssModule_16r0j_1\\">This text is styled by CSS Modules</p><p class=\\"global-configured-sass\\">This text is styled by global configured SASS</p><p class=\\"imported-sass\\">This text is styled by imported SASS</p><button class=\\"css-s689uo-App\\">Hover to change color.</button><p class=\\"css-2m18qq\\">Styled by Emotion</p><p class=\\"c-gqdJwI\\">Styled by Stiches</p><p><button data-testid=\\"increase\\" type=\\"button\\">count is: <div data-testid=\\"count\\">0</div></button></p><p>Edit <code>App.tsx</code> and save to test HMR updates.</p><p><a class=\\"App-link\\" href=\\"https://reactjs.org\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Learn React</a> | <a class=\\"App-link\\" href=\\"https://vitejs.dev/guide/features.html\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Vite Docs</a></p></header></div></div></body>"`, + `"<body><div><div class=\\"App\\"><header class=\\"App-header\\"><img src=\\"/logo.svg\\" class=\\"App-logo\\" alt=\\"logo\\"><img src=\\"/demo/assets/images/logo.svg\\" class=\\"logo2\\" alt=\\"logo2\\"><p>Hello Vite + React!</p><p class=\\"sc-bczRLJ dgihId\\">This text is styled by styled-components</p><p class=\\"global-css\\">This text is styled by global css which is not imported to App.tsx</p><p class=\\"_cssModule_16r0j_1\\">This text is styled by CSS Modules</p><p class=\\"global-configured-sass\\">This text is styled by global configured SASS</p><p class=\\"imported-sass\\">This text is styled by imported SASS</p><button class=\\"css-s689uo-App\\">Hover to change color.</button><p class=\\"css-2m18qq\\">Styled by Emotion</p><p class=\\"c-gqdJwI\\">Styled by Stiches</p><p class=\\"load-path-sass\\">This text is styled by SASS from load paths</p><p class=\\"animate__animated animate__bounce\\">An animated element style using @use ~</p><div class=\\"animated fadeIn\\"><p>An animated element style using import ~</p><p>Watch me fade in!</p></div><p><button data-testid=\\"increase\\" type=\\"button\\">count is: <div data-testid=\\"count\\">0</div></button></p><p>Edit <code>App.tsx</code> and save to test HMR updates.</p><p><a class=\\"App-link\\" href=\\"https://reactjs.org\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Learn React</a> | <a class=\\"App-link\\" href=\\"https://vitejs.dev/guide/features.html\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Vite Docs</a></p></header></div></div></body>"`, ); }); }); diff --git a/demo/assets/_scss/loadPathsExample/load-path.scss b/demo/assets/_scss/loadPathsExample/load-path.scss new file mode 100644 index 00000000..cad1255f --- /dev/null +++ b/demo/assets/_scss/loadPathsExample/load-path.scss @@ -0,0 +1,5 @@ +header { + .load-path-sass { + color: red; + } +} diff --git a/demo/assets/_scss/style.scss b/demo/assets/_scss/style.scss index 7aec78df..0db3cdaa 100644 --- a/demo/assets/_scss/style.scss +++ b/demo/assets/_scss/style.scss @@ -1,4 +1,8 @@ @use 'partials/partial-style'; // Example using @use +@use 'load-path'; // Example of loadPath config + +$use-fadeIn: true; +@import '~animate-sass/animate'; // Example of import ~ header { .imported-sass { diff --git a/examples/create-react-app/.env b/examples/create-react-app/.env new file mode 100644 index 00000000..0ba10db4 --- /dev/null +++ b/examples/create-react-app/.env @@ -0,0 +1,5 @@ +<!-- Use SASS_PATH causes CRA throw 1 warning, it looks like this is an issue of CRA +Reference: https://github.com/facebook/create-react-app/issues/12329 --> + +SASS_PATH=src/assets/\_scss/loadPathsExample +NODE_PATH=node_modules/ diff --git a/examples/create-react-app/package.json b/examples/create-react-app/package.json index 2e146148..b51fe525 100644 --- a/examples/create-react-app/package.json +++ b/examples/create-react-app/package.json @@ -45,7 +45,9 @@ ] }, "devDependencies": { + "animate-sass": "^0.8.2", "jest-preview": "file:../..", - "npm-run-all": "^4.1.5" + "npm-run-all": "^4.1.5", + "sass": "^1.50.0" } } diff --git a/examples/create-react-app/src/App.tsx b/examples/create-react-app/src/App.tsx index b1caf790..70036053 100644 --- a/examples/create-react-app/src/App.tsx +++ b/examples/create-react-app/src/App.tsx @@ -6,6 +6,7 @@ import styles from './style.module.css'; import './App.css'; import './assets/css/App.css'; +import './assets/_scss/style.scss'; function App() { const [count, setCount] = useState(0); @@ -17,6 +18,17 @@ function App() { <img src={logo2} className="logo2" alt="logo2" /> <p>Create React App example</p> <p className={styles.textOrange}>Styled by CSS Modules</p> + <p className="global-configured-sass"> + This text is styled by global configured SASS + </p> + <p className="imported-sass">This text is styled by imported SASS</p> + <p className="load-path-sass"> + This text is styled by SASS from load paths + </p> + <div className="animated fadeIn"> + <p>An animated element style using @import ~</p> + <p>Watch me fade in!</p> + </div> <button data-testid="increase" type="button" diff --git a/examples/create-react-app/src/__tests__/transform.test.tsx b/examples/create-react-app/src/__tests__/transform.test.tsx index 40def521..0f5418d0 100644 --- a/examples/create-react-app/src/__tests__/transform.test.tsx +++ b/examples/create-react-app/src/__tests__/transform.test.tsx @@ -7,7 +7,7 @@ describe('transform', () => { render(<App />); expect(document.body.outerHTML).toMatchInlineSnapshot( - `"<body><div><div class=\\"App\\"><header class=\\"App-header\\"><svg class=\\"svg-component\\">/src/logo.svg</svg><img src=\\"/src/logo.svg\\" class=\\"App-logo\\" alt=\\"logo\\"><img src=\\"/src/assets/images/logo.svg\\" class=\\"logo2\\" alt=\\"logo2\\"><p>Create React App example</p><p class=\\"_textOrange_1gpw2_1\\">Styled by CSS Modules</p><button data-testid=\\"increase\\" type=\\"button\\">count is: <div data-testid=\\"count\\">0</div></button><a class=\\"App-link\\" href=\\"https://reactjs.org\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Learn React</a></header></div></div></body>"`, + `"<body><div><div class=\\"App\\"><header class=\\"App-header\\"><svg class=\\"svg-component\\">/src/logo.svg</svg><img src=\\"/src/logo.svg\\" class=\\"App-logo\\" alt=\\"logo\\"><img src=\\"/src/assets/images/logo.svg\\" class=\\"logo2\\" alt=\\"logo2\\"><p>Create React App example</p><p class=\\"_textOrange_1gpw2_1\\">Styled by CSS Modules</p><p class=\\"global-configured-sass\\">This text is styled by global configured SASS</p><p class=\\"imported-sass\\">This text is styled by imported SASS</p><p class=\\"load-path-sass\\">This text is styled by SASS from load paths</p><div class=\\"animated fadeIn\\"><p>An animated element style using @import ~</p><p>Watch me fade in!</p></div><button data-testid=\\"increase\\" type=\\"button\\">count is: <div data-testid=\\"count\\">0</div></button><a class=\\"App-link\\" href=\\"https://reactjs.org\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Learn React</a></header></div></div></body>"`, ); }); }); diff --git a/examples/create-react-app/src/assets/_scss/global-style.scss b/examples/create-react-app/src/assets/_scss/global-style.scss new file mode 100644 index 00000000..ca28b4cd --- /dev/null +++ b/examples/create-react-app/src/assets/_scss/global-style.scss @@ -0,0 +1,7 @@ +@import 'partials/partial-global-style'; // Example using @import + +header { + .global-configured-sass { + color: yellow; + } +} diff --git a/examples/create-react-app/src/assets/_scss/loadPathsExample/load-path.scss b/examples/create-react-app/src/assets/_scss/loadPathsExample/load-path.scss new file mode 100644 index 00000000..cad1255f --- /dev/null +++ b/examples/create-react-app/src/assets/_scss/loadPathsExample/load-path.scss @@ -0,0 +1,5 @@ +header { + .load-path-sass { + color: red; + } +} diff --git a/examples/create-react-app/src/assets/_scss/partials/_partial-global-style.scss b/examples/create-react-app/src/assets/_scss/partials/_partial-global-style.scss new file mode 100644 index 00000000..294861fc --- /dev/null +++ b/examples/create-react-app/src/assets/_scss/partials/_partial-global-style.scss @@ -0,0 +1,5 @@ +header { + .global-configured-sass { + text-transform: uppercase; + } +} diff --git a/examples/create-react-app/src/assets/_scss/partials/_partial-style.scss b/examples/create-react-app/src/assets/_scss/partials/_partial-style.scss new file mode 100644 index 00000000..ae22fed5 --- /dev/null +++ b/examples/create-react-app/src/assets/_scss/partials/_partial-style.scss @@ -0,0 +1,5 @@ +header { + .imported-sass { + text-transform: uppercase; + } +} diff --git a/examples/create-react-app/src/assets/_scss/style.scss b/examples/create-react-app/src/assets/_scss/style.scss new file mode 100644 index 00000000..0db3cdaa --- /dev/null +++ b/examples/create-react-app/src/assets/_scss/style.scss @@ -0,0 +1,11 @@ +@use 'partials/partial-style'; // Example using @use +@use 'load-path'; // Example of loadPath config + +$use-fadeIn: true; +@import '~animate-sass/animate'; // Example of import ~ + +header { + .imported-sass { + color: pink; + } +} diff --git a/examples/create-react-app/src/index.tsx b/examples/create-react-app/src/index.tsx index ef2edf8e..c3229d4c 100644 --- a/examples/create-react-app/src/index.tsx +++ b/examples/create-react-app/src/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; +import './assets/_scss/global-style.scss'; import App from './App'; import reportWebVitals from './reportWebVitals'; @@ -8,7 +9,7 @@ ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, - document.getElementById('root') + document.getElementById('root'), ); // If you want to start measuring performance in your app, pass a function diff --git a/examples/create-react-app/src/setupTests.ts b/examples/create-react-app/src/setupTests.ts index c4eeb3b4..faaab917 100644 --- a/examples/create-react-app/src/setupTests.ts +++ b/examples/create-react-app/src/setupTests.ts @@ -6,7 +6,8 @@ import '@testing-library/jest-dom'; import { jestPreviewConfigure } from 'jest-preview'; jestPreviewConfigure({ - externalCss: ['src/index.css'], + externalCss: ['src/index.css', 'src/assets/_scss/global-style.scss'], + sassLoadPaths: ['src/assets/_scss/loadPathsExample'], }); window.matchMedia = (query) => ({ diff --git a/examples/vite-react/config/jest/setupTests.js b/examples/vite-react/config/jest/setupTests.js index 22bde1d2..046c2db5 100644 --- a/examples/vite-react/config/jest/setupTests.js +++ b/examples/vite-react/config/jest/setupTests.js @@ -5,6 +5,7 @@ import { jestPreviewConfigure } from 'jest-preview'; jestPreviewConfigure({ externalCss: ['src/index.css', 'src/assets/_scss/global-style.scss'], autoPreview: true, + sassLoadPaths: ['src/assets/_scss/loadPathsExample'], }); window.matchMedia = (query) => ({ diff --git a/examples/vite-react/jest.config.js b/examples/vite-react/jest.config.js index cd879837..abb20ab9 100644 --- a/examples/vite-react/jest.config.js +++ b/examples/vite-react/jest.config.js @@ -23,6 +23,8 @@ module.exports = { '^react-native$': 'react-native-web', // Used to dedupe `styled-component` when run `npm link` in development '^styled-components$': '<rootDir>/node_modules/styled-components', + // Support import ~ + '^~(.*)': '<rootDir>/node_modules/$1', }, moduleFileExtensions: [ // Place tsx and ts to beginning as suggestion from Jest team diff --git a/examples/vite-react/package.json b/examples/vite-react/package.json index 69971af3..bfcd0aaf 100644 --- a/examples/vite-react/package.json +++ b/examples/vite-react/package.json @@ -25,6 +25,8 @@ "@types/react": "^17.0.33", "@types/react-dom": "^17.0.10", "@vitejs/plugin-react": "^1.0.7", + "animate-sass": "^0.8.2", + "animate.css": "^4.1.1", "jest": "^27.5.1", "jest-preview": "file:../..", "jest-watch-typeahead": "^1.0.0", diff --git a/examples/vite-react/src/App.tsx b/examples/vite-react/src/App.tsx index f5feda58..d2e6a3db 100644 --- a/examples/vite-react/src/App.tsx +++ b/examples/vite-react/src/App.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import styled from 'styled-components'; +import '~animate.css/animate.css'; import logo2 from './assets/images/logo.svg'; @@ -23,6 +24,16 @@ function App() { This text is styled by global configured SASS </p> <p className="imported-sass">This text is styled by imported SASS</p> + <p className="load-path-sass"> + This text is styled by SASS from load paths + </p> + <p className="animate__animated animate__bounce"> + An animated element style using @use ~ + </p> + <div className="animated fadeIn"> + <p>An animated element style using import ~</p> + <p>Watch me fade in!</p> + </div> <p> <button data-testid="increase" diff --git a/examples/vite-react/src/assets/_scss/loadPathsExample/load-path.scss b/examples/vite-react/src/assets/_scss/loadPathsExample/load-path.scss new file mode 100644 index 00000000..cad1255f --- /dev/null +++ b/examples/vite-react/src/assets/_scss/loadPathsExample/load-path.scss @@ -0,0 +1,5 @@ +header { + .load-path-sass { + color: red; + } +} diff --git a/examples/vite-react/src/assets/_scss/style.scss b/examples/vite-react/src/assets/_scss/style.scss index 7aec78df..0db3cdaa 100644 --- a/examples/vite-react/src/assets/_scss/style.scss +++ b/examples/vite-react/src/assets/_scss/style.scss @@ -1,4 +1,8 @@ @use 'partials/partial-style'; // Example using @use +@use 'load-path'; // Example of loadPath config + +$use-fadeIn: true; +@import '~animate-sass/animate'; // Example of import ~ header { .imported-sass { diff --git a/examples/vite-react/vite.config.ts b/examples/vite-react/vite.config.ts index 6ed15186..4e5100c1 100644 --- a/examples/vite-react/vite.config.ts +++ b/examples/vite-react/vite.config.ts @@ -26,5 +26,22 @@ export default defineConfig(({ mode }) => { // If `envWithProcessPrefix` is an empty object, `process.env` will be undefined and the app cannot be loaded // Caveat: Cannot access `process.env` in build mode, always use `process.env.VARIABLE_NAME` define: envWithProcessPrefix, + // Support tilde import + resolve: { + alias: { + '~animate-sass': path.resolve(__dirname, 'node_modules/animate-sass'), + '~animate.css': path.resolve(__dirname, 'node_modules/animate.css'), + }, + }, + // Support loadPaths for scss + css: { + preprocessorOptions: { + scss: { + includePaths: [ + path.resolve(__dirname, 'demo/assets/_scss/loadPathsExample'), + ], + }, + }, + }, }; }); diff --git a/jest.config.js b/jest.config.js index b820b91b..137e793d 100644 --- a/jest.config.js +++ b/jest.config.js @@ -21,6 +21,8 @@ module.exports = { modulePaths: ['<rootDir>/demo'], moduleNameMapper: { '^react-native$': 'react-native-web', + // Support import ~ + '^~(.*)': '<rootDir>/node_modules/$1', }, moduleFileExtensions: [ // Place tsx and ts to beginning as suggestion from Jest team diff --git a/package-lock.json b/package-lock.json index 883a7df3..9f184ace 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jest-preview", - "version": "0.2.3", + "version": "0.2.4-alpha.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "jest-preview", - "version": "0.2.3", + "version": "0.2.4-alpha.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -37,6 +37,8 @@ "@types/react-dom": "^17.0.10", "@types/styled-components": "^5.1.24", "@vitejs/plugin-react": "^1.3.0", + "animate-sass": "^0.8.2", + "animate.css": "^4.1.1", "cross-env": "^7.0.3", "jest": "^27.5.1", "jest-watch-typeahead": "^1.0.0", @@ -2003,6 +2005,18 @@ "node": ">= 6.0.0" } }, + "node_modules/animate-sass": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/animate-sass/-/animate-sass-0.8.2.tgz", + "integrity": "sha512-6sZ34dusqb1HdZLNpJUNaZc0CuuLgFI8SqlyaxbSSKE9zqfoNtOeO3IpVUCqvaSm/oMNH1VlEAsJlppjg+idKQ==", + "dev": true + }, + "node_modules/animate.css": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz", + "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==", + "dev": true + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -9845,6 +9859,18 @@ "debug": "4" } }, + "animate-sass": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/animate-sass/-/animate-sass-0.8.2.tgz", + "integrity": "sha512-6sZ34dusqb1HdZLNpJUNaZc0CuuLgFI8SqlyaxbSSKE9zqfoNtOeO3IpVUCqvaSm/oMNH1VlEAsJlppjg+idKQ==", + "dev": true + }, + "animate.css": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz", + "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==", + "dev": true + }, "ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", diff --git a/package.json b/package.json index f4a0a742..194dff79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jest-preview", - "version": "0.2.3", + "version": "0.2.4-alpha.0", "description": "Preview your HTML code while using Jest", "keywords": [ "testing", @@ -78,6 +78,8 @@ "@types/styled-components": "^5.1.24", "@vitejs/plugin-react": "^1.3.0", "cross-env": "^7.0.3", + "animate-sass": "^0.8.2", + "animate.css": "^4.1.1", "jest": "^27.5.1", "jest-watch-typeahead": "^1.0.0", "nodemon": "^2.0.15", diff --git a/src/configure.ts b/src/configure.ts index 936516c5..da62ef80 100644 --- a/src/configure.ts +++ b/src/configure.ts @@ -2,7 +2,7 @@ import path from 'path'; import fs from 'fs'; import { exec } from 'child_process'; -import { CACHE_FOLDER } from './constants'; +import { CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG } from './constants'; import { createCacheFolderIfNeeded } from './utils'; import { debug } from './preview'; @@ -10,6 +10,7 @@ interface JestPreviewConfigOptions { externalCss?: string[]; autoPreview?: boolean; publicFolder?: string; + sassLoadPaths?: string[]; } export async function jestPreviewConfigure( @@ -17,7 +18,12 @@ export async function jestPreviewConfigure( externalCss = [], autoPreview = false, publicFolder, - }: JestPreviewConfigOptions = { externalCss: [], autoPreview: false }, + sassLoadPaths, + }: JestPreviewConfigOptions = { + externalCss: [], + autoPreview: false, + sassLoadPaths: [], + }, ) { if (autoPreview) { autoRunPreview(); @@ -29,6 +35,21 @@ export async function jestPreviewConfigure( }); } + let sassLoadPathsConfig: string[] = []; + // Save sassLoadPathsConfig to cache, so we can use it in the transformer + if (sassLoadPaths) { + sassLoadPathsConfig = sassLoadPaths.map( + (path) => `${process.cwd()}/${path}`, + ); + + createCacheFolderIfNeeded(); + + fs.writeFileSync( + path.join(CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG), + JSON.stringify(sassLoadPathsConfig), + ); + } + externalCss?.forEach((cssFile) => { // Avoid name collision // Example: src/common/styles.css => cache-src___common___styles.css @@ -45,19 +66,27 @@ export async function jestPreviewConfigure( '.css', ); + const sassLoadPathsCLIConfig = sassLoadPathsConfig.reduce( + (currentConfig, nextLoadPath) => + `${currentConfig} --load-path ${nextLoadPath}`, + '', + ); + // Transform sass to css and save to cache folder // We use exec instead of sass.compile because running sass.compile in jsdom environment cause unexpected behavior // What we encountered is that filename is automatically added `http://localhost` as the prefix // Example: style.scss => http://localhost/style.scss // As a result, sass.compile cannot find the file // TODO: Can we inject css to the `document.head` directly? + // TODO: Support import ~ for configured scss + // Currently, we cannot find the option to pass `importer` to sass CLI: https://sass-lang.com/documentation/cli/dart-sass#options exec( `${path.join( process.cwd(), 'node_modules', '.bin', 'sass', - )} ${cssFile} ${cssDestinationFile} --no-source-map`, + )} ${cssFile} ${cssDestinationFile} --no-source-map ${sassLoadPathsCLIConfig}`, (err: any) => { if (err) { console.log(err); diff --git a/src/constants.ts b/src/constants.ts index df84d544..dae98cb0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1,2 @@ export const CACHE_FOLDER = './node_modules/.cache/jest-preview'; +export const SASS_LOAD_PATHS_CONFIG = 'cache-sass-load-paths.config'; diff --git a/src/transform.ts b/src/transform.ts index 59797208..c7da5b66 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,6 +1,9 @@ +import fs from 'fs'; import path from 'path'; +import { pathToFileURL } from 'url'; import camelcase from 'camelcase'; import slash from 'slash'; +import { CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG } from './constants'; function getRelativeFilename(filename: string): string { return slash(filename.split(process.cwd())[1]); @@ -146,7 +149,36 @@ function processSass(src: string, filename: string): TransformedSource { }; } - const cssResult = sass.compile(filename).css; + const sassLoadPathsConfigPath = path.join( + CACHE_FOLDER, + SASS_LOAD_PATHS_CONFIG, + ); + + let sassLoadPathsConfig: string[]; + if (fs.existsSync(sassLoadPathsConfigPath)) { + const sassLoadPathsString = fs + .readFileSync(path.join(CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG), 'utf8') + .trim(); + sassLoadPathsConfig = JSON.parse(sassLoadPathsString); + } else { + sassLoadPathsConfig = []; + } + + const cssResult = sass.compile(filename, { + loadPaths: sassLoadPathsConfig, + importers: [ + { + // An importer that redirects relative URLs starting with "~" to `node_modules` + // Reference: https://sass-lang.com/documentation/js-api/interfaces/FileImporter + findFileUrl(url: string) { + if (!url.startsWith('~')) return null; + return new URL( + path.join(pathToFileURL('node_modules').href, url.substring(1)), + ); + }, + }, + ], + }).css; return { code: `const style = document.createElement('style'); diff --git a/vite.config.ts b/vite.config.ts index 4c3c1a53..9149a43f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -26,6 +26,23 @@ export default defineConfig(({ mode }) => { // If `envWithProcessPrefix` is an empty object, `process.env` will be undefined and the app cannot be loaded // Caveat: Cannot access `process.env` in build mode, always use `process.env.VARIABLE_NAME` define: envWithProcessPrefix, + // Support tilde import + resolve: { + alias: { + '~animate-sass': path.resolve(__dirname, 'node_modules/animate-sass'), + '~animate.css': path.resolve(__dirname, 'node_modules/animate.css'), + }, + }, + // Support loadPaths for scss + css: { + preprocessorOptions: { + scss: { + includePaths: [ + path.resolve(__dirname, 'demo/assets/_scss/loadPathsExample'), + ], + }, + }, + }, build: { lib: { entry: path.resolve(__dirname, 'src/index.ts'), diff --git a/website/docs/api/jestPreviewConfigure.md b/website/docs/api/jestPreviewConfigure.md index afec6bc2..96872519 100644 --- a/website/docs/api/jestPreviewConfigure.md +++ b/website/docs/api/jestPreviewConfigure.md @@ -39,6 +39,20 @@ jestPreviewConfigure({ ], ``` +## sassLoadPaths: string[] + +Default: `[]` + +Paths in which to look for stylesheets loaded by rules like `@use` and `@import` in sass files should be configured via `sassLoadPaths` option. They should be path from root of your project. For example: + +```js +jestPreviewConfigure({ + // Configure Sass load paths + sassLoadPaths: [ + 'demo/assets/_scss/loadPathsExample', + ], +``` + ## publicFolder: string Default: `undefined`.