Skip to content

Commit

Permalink
feat: Support scss load paths, tilde import
Browse files Browse the repository at this point in the history
  • Loading branch information
ntt261298 committed May 27, 2022
1 parent 3e23b9c commit d91c75f
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 4 deletions.
1 change: 1 addition & 0 deletions config/jest/setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => ({
Expand Down
11 changes: 11 additions & 0 deletions demo/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions demo/assets/_scss/loadPathsExample/load-path.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
header {
.load-path-sass {
color: red;
}
}
4 changes: 4 additions & 0 deletions demo/assets/_scss/style.scss
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
33 changes: 30 additions & 3 deletions src/configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@ 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';

interface JestPreviewConfigOptions {
externalCss?: string[];
autoPreview?: boolean;
publicFolder?: string;
sassLoadPaths?: string[];
}

export async function jestPreviewConfigure(
{
externalCss = [],
autoPreview = false,
publicFolder,
}: JestPreviewConfigOptions = { externalCss: [], autoPreview: false },
sassLoadPaths,
}: JestPreviewConfigOptions = {
externalCss: [],
autoPreview: false,
sassLoadPaths: [],
},
) {
if (autoPreview) {
autoRunPreview();
Expand All @@ -29,6 +35,19 @@ export async function jestPreviewConfigure(
});
}

let sassLoadFullPaths: string[] = [];
// Save sassLoadPaths to cache, so we can use it in the transformer
if (sassLoadPaths) {
sassLoadFullPaths = sassLoadPaths.map((path) => `${process.cwd()}/${path}`);

createCacheFolderIfNeeded();

fs.writeFileSync(
path.join(CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG),
JSON.stringify(sassLoadFullPaths),
);
}

externalCss?.forEach((cssFile) => {
// Avoid name collision
// Example: src/common/styles.css => cache-src___common___styles.css
Expand All @@ -45,19 +64,27 @@ export async function jestPreviewConfigure(
'.css',
);

const sassLoadPathsConfig = sassLoadFullPaths.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 ${sassLoadPathsConfig}`,
(err: any) => {
if (err) {
console.log(err);
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const CACHE_FOLDER = './node_modules/.cache/jest-preview';
export const SASS_LOAD_PATHS_CONFIG = 'cache-sass-load-paths.config';
33 changes: 32 additions & 1 deletion src/transform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
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]);
Expand Down Expand Up @@ -146,7 +148,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 sassLoadPaths: string[];
if (fs.existsSync(sassLoadPathsConfigPath)) {
const sassLoadPathsString = fs
.readFileSync(path.join(CACHE_FOLDER, SASS_LOAD_PATHS_CONFIG), 'utf8')
.trim();
sassLoadPaths = JSON.parse(sassLoadPathsString);
} else {
sassLoadPaths = [];
}

const cssResult = sass.compile(filename, {
loadPaths: sassLoadPaths,
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');
Expand Down
17 changes: 17 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down

0 comments on commit d91c75f

Please sign in to comment.