From c5d25a70cc63ca02c3f0320dfe9e4b885a3dfa41 Mon Sep 17 00:00:00 2001 From: Radoslav Karaivanov Date: Wed, 18 Sep 2024 12:26:18 +0300 Subject: [PATCH] build: Refactor styles and themes build pipeline (#1390) Cherry-picked from #1374 --- .storybook/preview.ts | 41 +++++++---------- scripts/build-styles.mjs | 66 +-------------------------- scripts/build.mjs | 7 ++- scripts/sass.mjs | 96 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 116 insertions(+), 94 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 56ca0421e..37517c17a 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,6 +1,6 @@ /// -import { html } from 'lit'; +import { type CSSResult, html } from 'lit'; import { configureTheme } from '../src/theming/config'; import type { Decorator } from '@storybook/web-components'; import { withActions } from '@storybook/addon-actions/decorator'; @@ -11,21 +11,24 @@ configureActions({ limit: 5, }); -type ThemeImport = { default: string }; +type ThemeImport = { styles: CSSResult }; -const themes = import.meta.glob('../src/styles/themes/**/*.scss', { - query: '?inline', -}); - -const getTheme = async ({ theme, variant }) => { - const matcher = `../src/styles/themes/${variant}/${theme}.scss`; +const themes = import.meta.glob( + '../src/styles/themes/**/*.css.ts', + { + eager: true, + import: 'styles', + } +); - const [_, resolver] = Object.entries(themes).find(([path]) => { - return path.match(matcher); - })!; +const getTheme = ({ theme, variant }) => { + const matcher = `../src/styles/themes/${variant}/${theme}.css.ts`; - const stylesheet = await resolver(); - return stylesheet.default; + for (const [path, styles] of Object.entries(themes)) { + if (path === matcher) { + return styles; + } + } }; const getSize = (size: 'small' | 'medium' | 'large' | 'default') => { @@ -81,22 +84,10 @@ export const globalTypes = { }, }; -const parser = new DOMParser(); - export const parameters = { backgrounds: { disable: true, }, - docs: { - source: { - // Strip theme styles and wrapping container from the code preview - transform: (code: string) => - parser.parseFromString(code, 'text/html').querySelector('#igc-story') - ?.innerHTML, - format: 'html', - language: 'html', - }, - }, }; export const loaders = [ diff --git a/scripts/build-styles.mjs b/scripts/build-styles.mjs index 09ac21954..b1af8cf39 100644 --- a/scripts/build-styles.mjs +++ b/scripts/build-styles.mjs @@ -1,65 +1,3 @@ -import { mkdirSync as makeDir } from 'node:fs'; -import { writeFile } from 'node:fs/promises'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { globby } from 'globby'; -import * as sass from 'sass-embedded'; -import report from './report.mjs'; -import { compileSass, fromTemplate } from './sass.mjs'; +import { buildComponents, buildThemes } from './sass.mjs'; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const DEST_DIR = path.join.bind(null, path.resolve(__dirname, '../dist')); - -export async function buildThemes() { - const compiler = await sass.initAsyncCompiler(); - const paths = await globby('src/styles/themes/{light,dark}/*.scss'); - - for (const sassFile of paths) { - const css = await compileSass(sassFile, compiler); - - const outputFile = DEST_DIR( - sassFile.replace(/\.scss$/, '.css').replace('src/styles/', '') - ); - makeDir(path.dirname(outputFile), { recursive: true }); - await writeFile(outputFile, css, 'utf-8'); - } - - await compiler.dispose(); -} - -(async () => { - const compiler = await sass.initAsyncCompiler(); - const start = performance.now(); - - const paths = await globby([ - 'src/components/**/*.base.scss', - 'src/components/**/*.common.scss', - 'src/components/**/*.shared.scss', - 'src/components/**/*.material.scss', - 'src/components/**/*.bootstrap.scss', - 'src/components/**/*.indigo.scss', - 'src/components/**/*.fluent.scss', - ]); - - try { - await Promise.all( - paths.map(async (path) => { - writeFile( - path.replace(/\.scss$/, '.css.ts'), - fromTemplate(await compileSass(path, compiler)), - 'utf8' - ); - }) - ); - } catch (err) { - await compiler.dispose(); - report.error(err); - process.exit(1); - } - - await compiler.dispose(); - - report.success( - `Styles generated in ${((performance.now() - start) / 1000).toFixed(2)}s` - ); -})(); +await Promise.all([buildThemes(), buildComponents()]); diff --git a/scripts/build.mjs b/scripts/build.mjs index 0fc99f2c2..cd93d29a9 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -9,8 +9,8 @@ import { getVsCodeHtmlCustomData, } from 'custom-element-vs-code-integration'; import customElements from '../custom-elements.json' assert { type: 'json' }; -import { buildThemes } from './build-styles.mjs'; import report from './report.mjs'; +import { buildComponents, buildThemes } from './sass.mjs'; const exec = promisify(_exec); @@ -39,7 +39,8 @@ async function runTask(tag, cmd) { (async () => { await runTask('Clean up', () => exec('npm run clean')); - await runTask('Styles', () => exec('npm run build:styles')); + await runTask('Component styles', () => buildComponents(true)); + await runTask('Themes', () => buildThemes(true)); // https://github.com/microsoft/TypeScript/issues/14619 await runTask('Components', () => @@ -48,8 +49,6 @@ async function runTask(tag, cmd) { ) ); - await runTask('Themes', buildThemes); - await runTask('Copying release files', () => Promise.all([ copyFile('scripts/_package.json', DEST_DIR('package.json')), diff --git a/scripts/sass.mjs b/scripts/sass.mjs index dabacf9de..7632c0e5b 100644 --- a/scripts/sass.mjs +++ b/scripts/sass.mjs @@ -1,7 +1,17 @@ -import { readFile } from 'node:fs/promises'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; import autoprefixer from 'autoprefixer'; +import { globby } from 'globby'; import postcss from 'postcss'; +import * as sass from 'sass-embedded'; +import report from './report.mjs'; + +const toDist = path.join.bind( + null, + path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../dist') +); const stripComments = () => { return { @@ -32,3 +42,87 @@ export async function compileSass(src, compiler) { const out = _postProcessor.process(compiled.css).css; return out.charCodeAt(0) === 0xfeff ? out.slice(1) : out; } + +export async function buildThemes(isProduction = false) { + const start = performance.now(); + + const [compiler, paths] = await Promise.all([ + sass.initAsyncCompiler(), + globby('src/styles/themes/{light,dark}/*.scss'), + ]); + + try { + await Promise.all( + paths.map(async (sassFile) => { + const outputFile = isProduction + ? toDist( + sassFile.replace(/\.scss$/, '.css').replace('src/styles/', '') + ) + : sassFile.replace(/\.scss$/, '.css.ts'); + + if (isProduction) { + await mkdir(path.dirname(outputFile), { recursive: true }); + writeFile(outputFile, await compileSass(sassFile, compiler), 'utf-8'); + } else { + writeFile( + outputFile, + fromTemplate(await compileSass(sassFile, compiler)), + 'utf-8' + ); + } + }) + ); + } catch (err) { + await compiler.dispose(); + report.error(err); + process.exit(1); + } + + await compiler.dispose(); + + if (!isProduction) { + report.success( + `Themes generated in ${((performance.now() - start) / 1000).toFixed(2)}s` + ); + } +} + +export async function buildComponents(isProduction = false) { + const start = performance.now(); + const [compiler, paths] = await Promise.all([ + sass.initAsyncCompiler(), + globby([ + 'src/components/**/*.base.scss', + 'src/components/**/*.common.scss', + 'src/components/**/*.shared.scss', + 'src/components/**/*.material.scss', + 'src/components/**/*.bootstrap.scss', + 'src/components/**/*.indigo.scss', + 'src/components/**/*.fluent.scss', + ]), + ]); + + try { + await Promise.all( + paths.map(async (path) => + writeFile( + path.replace(/\.scss$/, '.css.ts'), + fromTemplate(await compileSass(path, compiler)), + 'utf-8' + ) + ) + ); + } catch (err) { + await compiler.dispose(); + report.error(err); + process.exit(1); + } + + await compiler.dispose(); + + if (!isProduction) { + report.success( + `Component styles generated in ${((performance.now() - start) / 1000).toFixed(2)}s` + ); + } +}