diff --git a/scripts/generate-package b/scripts/generate-package index 4e14e1d..adece0b 100644 --- a/scripts/generate-package +++ b/scripts/generate-package @@ -14,6 +14,10 @@ const packages = [ manifest: { name: '@cloudscape-design/theming-build', main: './build/index.js', + exports: { + '.': './build/index.js', + './internal': './build/internal.js' + }, files: ['shared', 'build'], }, packageRoot: path.join(root, './lib/node'), diff --git a/src/build/__tests__/__fixtures__/scss/button/assets/icon.svg b/src/build/__tests__/__fixtures__/scss-advanced/button/assets/icon.svg similarity index 100% rename from src/build/__tests__/__fixtures__/scss/button/assets/icon.svg rename to src/build/__tests__/__fixtures__/scss-advanced/button/assets/icon.svg diff --git a/src/build/__tests__/__fixtures__/scss/button/styles.scss b/src/build/__tests__/__fixtures__/scss-advanced/button/styles.scss similarity index 100% rename from src/build/__tests__/__fixtures__/scss/button/styles.scss rename to src/build/__tests__/__fixtures__/scss-advanced/button/styles.scss diff --git a/src/build/__tests__/__fixtures__/scss/internal/base-component/styles.scss b/src/build/__tests__/__fixtures__/scss-advanced/internal/base-component/styles.scss similarity index 100% rename from src/build/__tests__/__fixtures__/scss/internal/base-component/styles.scss rename to src/build/__tests__/__fixtures__/scss-advanced/internal/base-component/styles.scss diff --git a/src/build/__tests__/__fixtures__/scss/internal/styles/tokens.scss b/src/build/__tests__/__fixtures__/scss-advanced/internal/styles/tokens.scss similarity index 100% rename from src/build/__tests__/__fixtures__/scss/internal/styles/tokens.scss rename to src/build/__tests__/__fixtures__/scss-advanced/internal/styles/tokens.scss diff --git a/src/build/__tests__/__fixtures__/scss-only/dependencies/commons.scss b/src/build/__tests__/__fixtures__/scss-only/dependencies/commons.scss new file mode 100644 index 0000000..dd44e3e --- /dev/null +++ b/src/build/__tests__/__fixtures__/scss-only/dependencies/commons.scss @@ -0,0 +1,3 @@ +.container { + background: #fff; +} diff --git a/src/build/__tests__/__fixtures__/scss-only/dependencies/styles.scss b/src/build/__tests__/__fixtures__/scss-only/dependencies/styles.scss new file mode 100644 index 0000000..2013de2 --- /dev/null +++ b/src/build/__tests__/__fixtures__/scss-only/dependencies/styles.scss @@ -0,0 +1,5 @@ +@use './commons'; + +.root { + inset: 0; +} diff --git a/src/build/__tests__/__fixtures__/scss-only/dir-structure/styles.scss b/src/build/__tests__/__fixtures__/scss-only/dir-structure/styles.scss new file mode 100644 index 0000000..93f2b4f --- /dev/null +++ b/src/build/__tests__/__fixtures__/scss-only/dir-structure/styles.scss @@ -0,0 +1,3 @@ +.root { + /* used in test-utils */ +} diff --git a/src/build/__tests__/__fixtures__/scss-only/dir-structure/sub-component/styles.scss b/src/build/__tests__/__fixtures__/scss-only/dir-structure/sub-component/styles.scss new file mode 100644 index 0000000..93f2b4f --- /dev/null +++ b/src/build/__tests__/__fixtures__/scss-only/dir-structure/sub-component/styles.scss @@ -0,0 +1,3 @@ +.root { + /* used in test-utils */ +} diff --git a/src/build/__tests__/__fixtures__/scss-only/inlines/styles.scss b/src/build/__tests__/__fixtures__/scss-only/inlines/styles.scss new file mode 100644 index 0000000..91ec639 --- /dev/null +++ b/src/build/__tests__/__fixtures__/scss-only/inlines/styles.scss @@ -0,0 +1,5 @@ +@use 'awsui:tokens' as tokens; + +.root { + background: tokens.$color-background; +} diff --git a/src/build/__tests__/__fixtures__/scss-only/simple/styles.scss b/src/build/__tests__/__fixtures__/scss-only/simple/styles.scss new file mode 100644 index 0000000..8780948 --- /dev/null +++ b/src/build/__tests__/__fixtures__/scss-only/simple/styles.scss @@ -0,0 +1,9 @@ +$root-color: red; + +.root { + /* used in tests */ +} + +.red-text { + color: $root-color; +} diff --git a/src/build/__tests__/common.ts b/src/build/__tests__/common.ts index 19772e3..38eb833 100644 --- a/src/build/__tests__/common.ts +++ b/src/build/__tests__/common.ts @@ -9,7 +9,7 @@ import { preset as _presetWithSecondaryTheme } from './__fixtures__/template/int export const preset = _preset as ThemePreset; export const presetWithSecondaryTheme = _presetWithSecondaryTheme as ThemePreset; export const outputDir = join(__dirname, 'out'); -export const scssDir = join(__dirname, '__fixtures__', 'scss'); +export const scssDir = join(__dirname, '__fixtures__', 'scss-advanced'); export const templateDir = join(__dirname, '__fixtures__', 'template'); export const designTokensTemplateDir = join(__dirname, '__fixtures__', 'template-tokens'); diff --git a/src/build/__tests__/styles.test.ts b/src/build/__tests__/styles.test.ts new file mode 100644 index 0000000..6ce436c --- /dev/null +++ b/src/build/__tests__/styles.test.ts @@ -0,0 +1,66 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { expect, test } from 'vitest'; +import { buildStyles, InlineStylesheet } from '../internal'; +import { join } from 'node:path'; +import { readFileSync, readdirSync } from 'node:fs'; + +const fixturesRoot = join(__dirname, '__fixtures__', 'scss-only'); +const outputRoot = join(__dirname, 'out', 'scss-only'); + +async function buildWithFixtures(suiteName: string, inlines?: InlineStylesheet[]) { + const outDir = join(outputRoot, suiteName); + await buildStyles(join(fixturesRoot, suiteName), join(outputRoot, suiteName), inlines); + return outDir; +} + +test('simple styles build', async () => { + const outDir = await buildWithFixtures('simple'); + expect(readdirSync(outDir)).toEqual(['styles.css.js', 'styles.scoped.css', 'styles.selectors.js']); + const { default: styles } = await import(join(outDir, 'styles.css.js')); + expect(styles).toMatchInlineSnapshot(` + { + "red-text": "awsui_red-text_4px4j_e9pfl_5", + "root": "awsui_root_4px4j_e9pfl_1", + } + `); +}); + +test('bundles all imports for styles.scss entry point', async () => { + const outDir = await buildWithFixtures('dependencies'); + expect(readdirSync(outDir)).toEqual(['styles.css.js', 'styles.scoped.css', 'styles.selectors.js']); + const { default: styles } = await import(join(outDir, 'styles.css.js')); + // includes class names from both files in the fixture + expect(styles).toMatchInlineSnapshot(` + { + "container": "awsui_container_4px4j_1gm47_1", + "root": "awsui_root_4px4j_1gm47_5", + } + `); +}); + +test('supports virtual stylesheets', async () => { + const outDir = await buildWithFixtures('inlines', [{ url: 'awsui:tokens', contents: '$color-background: #abcabc' }]); + expect(readdirSync(outDir)).toEqual(['styles.css.js', 'styles.scoped.css', 'styles.selectors.js']); + const cssContent = readFileSync(join(outDir, 'styles.scoped.css'), 'utf8'); + expect(cssContent).toContain('#abcabc'); +}); + +test('throws an error if a virtual stylesheet is not defined', async () => { + // rejects with an error containing message 'Can\'t find stylesheet to import' + await expect(() => buildWithFixtures('inlines')).rejects.toThrowError(/awsui:tokens/); +}); + +test('mirrors directory structure of the sources', async () => { + const outDir = await buildWithFixtures('dir-structure'); + expect(readdirSync(outDir)).toEqual(['styles.css.js', 'styles.scoped.css', 'styles.selectors.js', 'sub-component']); + expect(readdirSync(join(outDir, 'sub-component'))).toEqual([ + 'styles.css.js', + 'styles.scoped.css', + 'styles.selectors.js', + ]); + const { default: rootStyles } = await import(join(outDir, 'styles.css.js')); + const { default: subComponentStyles } = await import(join(outDir, 'sub-component', 'styles.css.js')); + // ensure the same class name has different scoped name in different files + expect(rootStyles.root).not.toEqual(subComponentStyles.root); +}); diff --git a/src/build/internal.ts b/src/build/internal.ts index 15c6d7d..96fec12 100644 --- a/src/build/internal.ts +++ b/src/build/internal.ts @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { createStyles } from './tasks/style'; +import { buildStyles, InlineStylesheet } from './tasks/style'; import { createPresetFiles } from './tasks/preset'; import { createInternalTokenFiles } from './tasks/internal-tokens'; import { createPublicTokenFiles } from './tasks/public-tokens'; @@ -9,6 +9,8 @@ import { getInlineStylesheets } from './inline-stylesheets'; import { calculatePropertiesMap } from './properties'; import findNeededTokens from './needed-tokens'; +export { buildStyles, InlineStylesheet }; + export type Tasks = 'preset' | 'design-tokens'; export interface BuildThemedComponentsInternalParams { @@ -78,10 +80,10 @@ export async function buildThemedComponentsInternal(params: BuildThemedComponent const defaults = reduce(resolution, primary, defaultsReducer()); const propertiesMap = calculatePropertiesMap([primary, ...secondary], variablesMap); - const styleTask = createStyles( - getInlineStylesheets(primary, secondary, defaults, variablesMap, propertiesMap, neededTokens), + const styleTask = buildStyles( + scssDir, componentsOutputDir, - scssDir + getInlineStylesheets(primary, secondary, defaults, variablesMap, propertiesMap, neededTokens) ); const internalTokensTask = createInternalTokenFiles(primary, defaults, propertiesMap, exposed, componentsOutputDir); diff --git a/src/build/tasks/style.ts b/src/build/tasks/style.ts index 1b264a3..6ae67c9 100644 --- a/src/build/tasks/style.ts +++ b/src/build/tasks/style.ts @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import sass from 'sass'; +import * as sass from 'sass'; import glob from 'glob'; import { postCSSForEach, postCSSAfterAll, scopedFileExt } from './postcss'; import path from 'path'; @@ -12,7 +12,7 @@ export interface InlineStylesheet { contents: string; } -export async function createStyles(inlines: InlineStylesheet[], outputDir: string, sassDir: string) { +export async function buildStyles(sassDir: string, outputDir: string, inlines: InlineStylesheet[] = []) { const files = await promisify(glob)('**/styles.scss', { cwd: sassDir }); const compiler = createCompiler(inlines, outputDir, sassDir);