From 7f30e6ee88d4f23a9a386add17a7ac1a4ef04685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20H=C3=B8egh?= Date: Tue, 19 Dec 2023 15:59:26 +0100 Subject: [PATCH] feat: import local themes --- README.md | 45 +++++++++ examples/sb-theme-example/src/styles.css | 3 - examples/ui-theme-example/gatsby-config.mjs | 36 +++++++ examples/ui-theme-example/gatsby-config.ts | 17 ---- .../ui-theme-example/src/StyleImporter.ts | 1 - examples/ui-theme-example/src/pages/index.tsx | 1 - examples/ui-theme-example/src/styles.css | 3 - .../src/{pages => styles}/style.css | 0 .../src/styles/themes/theme-sbanken.scss | 3 + .../src/styles/themes/theme-ui.scss | 3 + package.json | 2 +- .../package.json | 6 +- .../src/collectThemes.ts | 97 +++++++++++++++---- .../src/config.ts | 12 +++ .../src/gatsby-node.ts | 66 ++++++++----- .../src/inlineScriptDev.mjs | 2 +- yarn.lock | 43 +++++++- 17 files changed, 269 insertions(+), 71 deletions(-) delete mode 100644 examples/sb-theme-example/src/styles.css create mode 100644 examples/ui-theme-example/gatsby-config.mjs delete mode 100644 examples/ui-theme-example/gatsby-config.ts delete mode 100644 examples/ui-theme-example/src/StyleImporter.ts delete mode 100644 examples/ui-theme-example/src/styles.css rename examples/ui-theme-example/src/{pages => styles}/style.css (100%) create mode 100644 examples/ui-theme-example/src/styles/themes/theme-sbanken.scss create mode 100644 examples/ui-theme-example/src/styles/themes/theme-ui.scss create mode 100644 packages/gatsby-plugin-eufemia-theme-handler/src/config.ts diff --git a/README.md b/README.md index ed2168d..5f4689f 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,9 @@ function ThemeProvider({ children }) { '**/*-theme-basis.*', ], + // (optional) An array of RegExp that defines what should be threaded and splitted into themes. + themeMatchers: [/\/themes\/[^/]*theme-([^/.]*)[/.]/], + // (optional) when set to false, all theme styles will be loaded as separate files. inlineDefaultTheme: true, @@ -89,6 +92,48 @@ function ThemeProvider({ children }) { } ``` +You can also import local themes. They need to start with `./` when defined in `filesGlobs` and the files need to include `theme-{theme-name}` in the name: + +```js +// Your Config +import { + filesGlobsFallback, + includeFilesFallback, +} from 'gatsby-plugin-eufemia-theme-handler/config.js' + +export default { + plugins: [ + 'gatsby-plugin-sass', + { + resolve: 'gatsby-plugin-eufemia-theme-handler', + options: { + verbose: true, + defaultTheme: 'ui', + storageId: 'eufemia-ui', + filesGlobs: [ + // Eufemia Styles + ...filesGlobsFallback, + + // Local themes + './**/styles/themes/**/*.css', + ], + includeFiles: [ + // Eufemia Styles + ...includeFilesFallback, + + // Local themes + '**/styles/themes/**/*.css', + ], + themes: { + ui: { name: 'DNB Eufemia' }, + sbanken: { name: 'Sbanken' }, + }, + }, + }, + ], +} +``` + You can also use the interceptor methods from inside your components: ```js diff --git a/examples/sb-theme-example/src/styles.css b/examples/sb-theme-example/src/styles.css deleted file mode 100644 index e11caef..0000000 --- a/examples/sb-theme-example/src/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - background-color: greenyellow; -} diff --git a/examples/ui-theme-example/gatsby-config.mjs b/examples/ui-theme-example/gatsby-config.mjs new file mode 100644 index 0000000..ab0968b --- /dev/null +++ b/examples/ui-theme-example/gatsby-config.mjs @@ -0,0 +1,36 @@ +import { + filesGlobsFallback, + includeFilesFallback, +} from 'gatsby-plugin-eufemia-theme-handler/config.js' + +export default { + plugins: [ + 'gatsby-plugin-sass', + { + resolve: 'gatsby-plugin-eufemia-theme-handler', + options: { + verbose: true, + defaultTheme: 'ui', + storageId: 'eufemia-ui', + filesGlobs: [ + // Eufemia Styles + ...filesGlobsFallback, + + // Local styles + './**/styles/themes/**/*', + ], + includeFiles: [ + // Eufemia Styles + ...includeFilesFallback, + + // Local styles + '**/styles/themes/**/*', + ], + themes: { + ui: { name: 'DNB Eufemia' }, + sbanken: { name: 'Sbanken' }, + }, + }, + }, + ], +} diff --git a/examples/ui-theme-example/gatsby-config.ts b/examples/ui-theme-example/gatsby-config.ts deleted file mode 100644 index a1d642d..0000000 --- a/examples/ui-theme-example/gatsby-config.ts +++ /dev/null @@ -1,17 +0,0 @@ -export default { - plugins: [ - 'gatsby-plugin-sass', - { - resolve: 'gatsby-plugin-eufemia-theme-handler', - options: { - verbose: true, - defaultTheme: 'ui', - storageId: 'eufemia-ui', - themes: { - ui: { name: 'DNB Eufemia' }, - sbanken: { name: 'Sbanken' }, - }, - }, - }, - ], -} diff --git a/examples/ui-theme-example/src/StyleImporter.ts b/examples/ui-theme-example/src/StyleImporter.ts deleted file mode 100644 index a246802..0000000 --- a/examples/ui-theme-example/src/StyleImporter.ts +++ /dev/null @@ -1 +0,0 @@ -import '@dnb/eufemia/style/core' diff --git a/examples/ui-theme-example/src/pages/index.tsx b/examples/ui-theme-example/src/pages/index.tsx index 1f8e981..b2aa1e6 100644 --- a/examples/ui-theme-example/src/pages/index.tsx +++ b/examples/ui-theme-example/src/pages/index.tsx @@ -3,7 +3,6 @@ import { Anchor, Button, Card } from '@dnb/eufemia' import { Form } from '@dnb/eufemia/extensions/forms' import ChangeStyleTheme from '../../../shared/ChangeStyleTheme' import { Link } from 'gatsby' -import './style.css' const App = () => { return ( diff --git a/examples/ui-theme-example/src/styles.css b/examples/ui-theme-example/src/styles.css deleted file mode 100644 index e11caef..0000000 --- a/examples/ui-theme-example/src/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - background-color: greenyellow; -} diff --git a/examples/ui-theme-example/src/pages/style.css b/examples/ui-theme-example/src/styles/style.css similarity index 100% rename from examples/ui-theme-example/src/pages/style.css rename to examples/ui-theme-example/src/styles/style.css diff --git a/examples/ui-theme-example/src/styles/themes/theme-sbanken.scss b/examples/ui-theme-example/src/styles/themes/theme-sbanken.scss new file mode 100644 index 0000000..dfaf056 --- /dev/null +++ b/examples/ui-theme-example/src/styles/themes/theme-sbanken.scss @@ -0,0 +1,3 @@ +.eufemia-theme__sbanken { + background: limegreen; +} diff --git a/examples/ui-theme-example/src/styles/themes/theme-ui.scss b/examples/ui-theme-example/src/styles/themes/theme-ui.scss new file mode 100644 index 0000000..8f2f193 --- /dev/null +++ b/examples/ui-theme-example/src/styles/themes/theme-ui.scss @@ -0,0 +1,3 @@ +.eufemia-theme__ui { + background: tomato; +} diff --git a/package.json b/package.json index 7ff797d..17e1034 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "start": "yarn watch & yarn workspace sb-theme-example start & yarn workspace ui-theme-example start", "build": "yarn workspace gatsby-plugin-eufemia-theme-handler build && yarn workspace ui-theme-example build && yarn workspace ui-theme-example serve & yarn workspace sb-theme-example build", "serve": "yarn workspace sb-theme-example serve & yarn workspace ui-theme-example serve", - "clean": "yarn workspace sb-theme-example clean & yarn workspace ui-theme-example clean", + "clean": "yarn workspace gatsby-plugin-eufemia-theme-handler clean && yarn workspace sb-theme-example clean & yarn workspace ui-theme-example clean", "release": "yarn workspace gatsby-plugin-eufemia-theme-handler release" }, "devDependencies": { diff --git a/packages/gatsby-plugin-eufemia-theme-handler/package.json b/packages/gatsby-plugin-eufemia-theme-handler/package.json index 6f12962..a3a4d28 100644 --- a/packages/gatsby-plugin-eufemia-theme-handler/package.json +++ b/packages/gatsby-plugin-eufemia-theme-handler/package.json @@ -29,6 +29,7 @@ }, "./collectThemes.js": "./collectThemes.js", "./themeHandler.js": "./themeHandler.js", + "./config.js": "./config.js", "./themeHandler.d.ts": "./themeHandler.d.ts", "./inlineScriptDev.js": "./inlineScriptDev.js", "./inlineScript.js": "./inlineScript.js", @@ -44,8 +45,8 @@ "build": "yarn build:cmd && GATSBY_FILES=true yarn build:cmd && yarn build:types", "build:cmd": "yarn babel src --extensions .js,.ts,.tsx,.mjs --out-dir .", "build:types": "tsc --project tsconfig.json", - "build:watch": "yarn build:cmd --watch", - "clean": "rm !(src|babel.config.js|.gitignore|.npmignore|LICENSE|.env|*.md|*.json)", + "build:watch": "concurrently 'yarn clean' 'yarn build:cmd --watch' 'GATSBY_FILES=true yarn build:cmd --watch' 'yarn build:types --watch'", + "clean": "rm !(src|babel.config.js|.gitignore|.npmignore|LICENSE|.env|*.md|*.json) & echo 1", "test:types": "tsc --noEmit" }, "dependencies": { @@ -61,6 +62,7 @@ "@babel/preset-env": "^7.23.6", "@babel/preset-react": "^7.23.3", "@babel/preset-typescript": "^7.23.3", + "concurrently": "8.2.2", "semantic-release": "21.1.1", "typescript": "5.3.3" }, diff --git a/packages/gatsby-plugin-eufemia-theme-handler/src/collectThemes.ts b/packages/gatsby-plugin-eufemia-theme-handler/src/collectThemes.ts index 46ff492..d1f4a6d 100644 --- a/packages/gatsby-plugin-eufemia-theme-handler/src/collectThemes.ts +++ b/packages/gatsby-plugin-eufemia-theme-handler/src/collectThemes.ts @@ -20,11 +20,13 @@ export function createThemesImport({ }) { const includeFiles = pluginOptions.includeFiles - const limitThemes = Object.keys(pluginOptions.themes || []) const packageRoot = path.dirname( require.resolve('@dnb/eufemia', { paths: [programDirectory] }) ) const globbyPaths = pluginOptions.filesGlobs.map((glob) => { + if (glob.startsWith('./')) { + return slash(path.join(programDirectory, glob)) + } return slash(path.join(packageRoot, glob)) }) @@ -36,24 +38,19 @@ export function createThemesImport({ ) } - const importFiles = globby - .sync(globbyPaths) - .map((file) => { - return slash(file) - }) - .filter((file) => { - if (/\/(es|cjs)\/style\//.test(file)) { - return false - } + const importFiles = globby.sync(globbyPaths).filter((file) => { + if (/\/(es|cjs)\/style\//.test(file)) { + return false + } - if (includeFiles.length > 0) { - return includeFiles.some((glob) => - micromatch.isMatch(file, '**/' + glob) - ) - } + if (includeFiles.length > 0) { + return includeFiles.some((glob) => + micromatch.isMatch(file, '**/' + glob) + ) + } - return true - }) + return true + }) if (pluginOptions.verbose) { reporter.info( @@ -94,6 +91,55 @@ export function createThemesImport({ ) } + /** + * Report about what Eufemia versions are found and used + */ + if (pluginOptions.verbose) { + const roots = {} + const versions = {} + + sortedImportFiles.forEach(({ file }) => { + const rootPath = findNearestPackageJson(file) + roots[rootPath] = [...(roots[rootPath] || []), file] + }) + + Object.entries(roots).forEach(([file, info]) => { + const content = fs.readFileSync(file, 'utf-8') + const json = JSON.parse(content) + const { name, version, dependencies, devDependencies } = json + + const collection = [ + name !== '@dnb/eufemia' ? 'local' : version, + dependencies?.['@dnb/eufemia'], + devDependencies?.['@dnb/eufemia'], + ].filter(Boolean) + + collection.forEach((version) => { + versions[version] = [...(versions[version] || []), info] + }) + }) + + const listOfVersions = Object.keys(versions).filter( + (version) => version !== 'local' + ) + + if (listOfVersions.length > 1) { + reporter.warn( + `gatsby-plugin-eufemia-theme-handler > @dnb/eufemia versions:\n${JSON.stringify( + versions, + null, + 2 + )}` + ) + } else { + reporter.info( + `gatsby-plugin-eufemia-theme-handler > @dnb/eufemia version: ${listOfVersions.join( + ', ' + )}` + ) + } + } + const writeThemesImports = () => { const imports = sortedImportFiles.map(({ file }) => { return `import '${file}'` @@ -124,3 +170,20 @@ export function createThemesImport({ showReports() } + +function findNearestPackageJson(filePath) { + let currentDir = path.dirname(filePath) + + while (currentDir !== '/') { + const packageJsonPath = path.join(currentDir, 'package.json') + + if (fs.existsSync(packageJsonPath)) { + return packageJsonPath + } + + // Move up one level in the directory tree + currentDir = path.dirname(currentDir) + } + + return null // No package.json found +} diff --git a/packages/gatsby-plugin-eufemia-theme-handler/src/config.ts b/packages/gatsby-plugin-eufemia-theme-handler/src/config.ts new file mode 100644 index 0000000..ce8a77b --- /dev/null +++ b/packages/gatsby-plugin-eufemia-theme-handler/src/config.ts @@ -0,0 +1,12 @@ +export const filesGlobsFallback = [ + '**/style/dnb-ui-core.min.css', + '**/style/themes/**/*-theme-{basis,components}.min.css', +] + +export const includeFilesFallback = [ + '**/dnb-ui-core.*', + '**/*-theme-components.*', + '**/*-theme-basis.*', +] + +export const themeMatchersFallback = [/\/themes\/[^/]*theme-([^/.]*)[/.]/] diff --git a/packages/gatsby-plugin-eufemia-theme-handler/src/gatsby-node.ts b/packages/gatsby-plugin-eufemia-theme-handler/src/gatsby-node.ts index 507c7f7..2c9b49c 100644 --- a/packages/gatsby-plugin-eufemia-theme-handler/src/gatsby-node.ts +++ b/packages/gatsby-plugin-eufemia-theme-handler/src/gatsby-node.ts @@ -7,26 +7,23 @@ import path from 'path' import micromatch from 'micromatch' import { slash } from 'gatsby-core-utils' import { createThemesImport } from './collectThemes' +import { + filesGlobsFallback, + includeFilesFallback, + themeMatchersFallback, +} from './config' global.themeNames = [] +global.reportMatches = [] exports.pluginOptionsSchema = ({ Joi }) => { return Joi.object({ themes: Joi.object().required(), defaultTheme: Joi.string().required(), storageId: Joi.string().optional().default('eufemia-theme'), - filesGlobs: Joi.array() - .optional() - .default([ - '**/style/dnb-ui-core.min.css', - '**/style/themes/**/*-theme-{basis,components}.min.css', - ]), - includeFiles: Joi.array().optional().default([ - // The file order does matter! - '**/dnb-ui-core.*', - '**/*-theme-components.*', - '**/*-theme-basis.*', - ]), + filesGlobs: Joi.array().optional().default(filesGlobsFallback), + includeFiles: Joi.array().optional().default(includeFilesFallback), + themeMatchers: Joi.array().optional().default(themeMatchersFallback), inlineDefaultTheme: Joi.boolean().optional().default(true), wrapWithThemeProvider: Joi.boolean().optional().default(true), omitScrollBehavior: Joi.boolean().optional().default(false), @@ -55,7 +52,7 @@ exports.onPostBuild = ({ reporter }) => { } exports.onCreateWebpackConfig = ( - { stage, actions, plugins, getConfig }, + { stage, reporter, actions, plugins, getConfig }, pluginOptions ) => { const config = getConfig() @@ -75,24 +72,49 @@ exports.onCreateWebpackConfig = ( ) if (stage === 'develop' || stage === 'build-javascript') { - const isInGlob = (fileName) => { - return pluginOptions.filesGlobs.some((glob) => - micromatch.isMatch(fileName, path.dirname(glob)) + const includeFilesMatcher = (file) => { + return pluginOptions.includeFiles.some((glob) => + micromatch.isMatch(file, path.dirname(glob)) ) } + const themeMatcher = (file) => { + let match = null + pluginOptions.themeMatchers.some( + (regexp) => (match = file.match(regexp)?.[1]) + ) + if (match) { + return match + } + return match + } + + const themes = Object.keys(pluginOptions.themes) + config.optimization.splitChunks.cacheGroups.styles = { ...config.optimization.splitChunks.cacheGroups.styles, name(module) { - const fileName = slash(module.context) - if (isInGlob(fileName)) { - const moduleName = fileName.match(/\/.*theme-([^/]*)$/)?.[1] + const file = slash(module._identifier.replace(/.*\]!(.*)/, '$1')) - if (moduleName && !global.themeNames.includes(moduleName)) { - global.themeNames.push(moduleName) + if (includeFilesMatcher(file)) { + const themeName = themeMatcher(file) + const match = themes.includes(themeName) + + if (pluginOptions.verbose) { + reporter.info( + `gatsby-plugin-eufemia-theme-handler > themeMatchers: ${JSON.stringify( + { themeName, match, file } + )}` + ) } - return moduleName || 'commons' + if (match) { + if (themeName && !global.themeNames.includes(themeName)) { + global.themeNames.push(themeName) + } + + return themeName + } } return 'commons' diff --git a/packages/gatsby-plugin-eufemia-theme-handler/src/inlineScriptDev.mjs b/packages/gatsby-plugin-eufemia-theme-handler/src/inlineScriptDev.mjs index 194b05b..3241b9f 100644 --- a/packages/gatsby-plugin-eufemia-theme-handler/src/inlineScriptDev.mjs +++ b/packages/gatsby-plugin-eufemia-theme-handler/src/inlineScriptDev.mjs @@ -1,7 +1,7 @@ if (typeof window !== 'undefined') { if (!window.__hasEufemiaObserver) { window.__hasEufemiaObserver = true - onElementInsertion('[href="/commons.css"]', () => { + onElementInsertion('[href*="/commons.css"]', () => { const themeName = globalThis.__getEufemiaThemeName() globalThis.__updateEufemiaThemeFile(themeName, true) }) diff --git a/yarn.lock b/yarn.lock index 0899d6f..3cf129a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6964,6 +6964,26 @@ __metadata: languageName: node linkType: hard +"concurrently@npm:8.2.2": + version: 8.2.2 + resolution: "concurrently@npm:8.2.2" + dependencies: + chalk: ^4.1.2 + date-fns: ^2.30.0 + lodash: ^4.17.21 + rxjs: ^7.8.1 + shell-quote: ^1.8.1 + spawn-command: 0.0.2 + supports-color: ^8.1.1 + tree-kill: ^1.2.2 + yargs: ^17.7.2 + bin: + conc: dist/bin/concurrently.js + concurrently: dist/bin/concurrently.js + checksum: 8ac774df06869773438f1bf91025180c52d5b53139bc86cf47659136c0d97461d0579c515d848d1e945d4e3e0cafe646b2ea18af8d74259b46abddcfe39b2c6c + languageName: node + linkType: hard + "config-chain@npm:^1.1.11": version: 1.1.13 resolution: "config-chain@npm:1.1.13" @@ -9561,6 +9581,7 @@ __metadata: "@babel/preset-env": ^7.23.6 "@babel/preset-react": ^7.23.3 "@babel/preset-typescript": ^7.23.3 + concurrently: 8.2.2 gatsby-core-utils: 4.12.1 globby: 11.0.4 micromatch: 4.0.5 @@ -16034,7 +16055,7 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.7.3": +"shell-quote@npm:^1.7.3, shell-quote@npm:^1.8.1": version: 1.8.1 resolution: "shell-quote@npm:1.8.1" checksum: 5f01201f4ef504d4c6a9d0d283fa17075f6770bfbe4c5850b074974c68062f37929ca61700d95ad2ac8822e14e8c4b990ca0e6e9272e64befd74ce5e19f0736b @@ -16314,6 +16335,13 @@ __metadata: languageName: node linkType: hard +"spawn-command@npm:0.0.2": + version: 0.0.2 + resolution: "spawn-command@npm:0.0.2" + checksum: e35c5d28177b4d461d33c88cc11f6f3a5079e2b132c11e1746453bbb7a0c0b8a634f07541a2a234fa4758239d88203b758def509161b651e81958894c0b4b64b + languageName: node + linkType: hard + "spawn-error-forwarder@npm:~1.0.0": version: 1.0.0 resolution: "spawn-error-forwarder@npm:1.0.0" @@ -16786,7 +16814,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0": +"supports-color@npm:^8.0.0, supports-color@npm:^8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -17123,6 +17151,15 @@ __metadata: languageName: node linkType: hard +"tree-kill@npm:^1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 + languageName: node + linkType: hard + "treeverse@npm:^3.0.0": version: 3.0.0 resolution: "treeverse@npm:3.0.0" @@ -18199,7 +18236,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.5.1": +"yargs@npm:^17.5.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: