diff --git a/cspell.json b/cspell.json index 5a76ee66..4ef02597 100644 --- a/cspell.json +++ b/cspell.json @@ -9,6 +9,9 @@ "atrule", "autocorrect", "autofix", + "autoload", + "autoloader", + "autoloading", "autoplay", "bezier", "boxicons", diff --git a/docs/resources/changelog.md b/docs/resources/changelog.md index 53d988f0..9b60d16f 100644 --- a/docs/resources/changelog.md +++ b/docs/resources/changelog.md @@ -8,6 +8,8 @@ New versions of Shoelace are released as-needed and generally occur when a criti ## Next +- Added an experimental autoloader +- Added the `subpath` argument to `getBasePath()` to make it easier to generate full paths to any file - Fixed a bug in `` that caused the display label to render incorrectly in Chrome after form validation [#1197](https://github.com/shoelace-style/shoelace/discussions/1197) - Fixed a bug in `` that prevented users from applying their own value for `autocapitalize`, `autocomplete`, and `autocorrect` when using `type="password` [#1205](https://github.com/shoelace-style/shoelace/issues/1205) diff --git a/scripts/build.js b/scripts/build.js index 61b81846..772d7585 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -51,6 +51,8 @@ fs.mkdirSync(outdir, { recursive: true }); // // The whole shebang './src/shoelace.ts', + // The auto-loader + './src/shoelace-autoloader.ts', // Components ...(await globby('./src/components/**/!(*.(style|test)).ts')), // Translations @@ -120,6 +122,22 @@ fs.mkdirSync(outdir, { recursive: true }); routes: { '/dist': './dist' } + }, + // + // Suppress Chrome's document.write() warning + // + // More info: https://github.com/BrowserSync/browser-sync/issues/1600) + // + snippetOptions: { + rule: { + match: /<\/head>/u, + fn: (snippet, match) => { + const { + groups: { src } + } = /src='(?[^']+)'/u.exec(snippet); + return `${match}`; + } + } } }; diff --git a/src/components/icon/library.default.ts b/src/components/icon/library.default.ts index 28f3672c..f001c61a 100644 --- a/src/components/icon/library.default.ts +++ b/src/components/icon/library.default.ts @@ -3,7 +3,7 @@ import type { IconLibrary } from './library'; const library: IconLibrary = { name: 'default', - resolver: name => `${getBasePath()}/assets/icons/${name}.svg` + resolver: name => getBasePath(`assets/icons/${name}.svg`) }; export default library; diff --git a/src/shoelace-autoloader.ts b/src/shoelace-autoloader.ts new file mode 100644 index 00000000..c9d98913 --- /dev/null +++ b/src/shoelace-autoloader.ts @@ -0,0 +1,58 @@ +import { getBasePath } from './utilities/base-path'; + +const observer = new MutationObserver(mutations => { + for (const { addedNodes } of mutations) { + for (const node of addedNodes) { + if (node.nodeType === Node.ELEMENT_NODE) { + discover(node as Element); + } + } + } +}); + +/** + * Checks a node for undefined elements and attempts to register them. + */ +async function discover(root: Element) { + const rootTagName = root.tagName.toLowerCase(); + const rootIsCustomElement = rootTagName.includes('-'); + const tags = [...root.querySelectorAll(':not(:defined)')] + .map(el => el.tagName.toLowerCase()) + .filter(tag => tag.startsWith('sl-')); + + // If the root element is an undefined custom element, add it to the list + if (rootIsCustomElement && !customElements.get(rootTagName)) { + tags.push(root.tagName.toLowerCase()); + } + + // Make the list unique + const tagsToRegister = [...new Set(tags)]; + + await Promise.allSettled(tagsToRegister.map(tagName => register(tagName))); +} + +/** + * Registers an element by tag name. + */ +function register(tagName: string): Promise { + const tagWithoutPrefix = tagName.replace(/^sl-/i, ''); + const path = getBasePath(`components/${tagWithoutPrefix}/${tagWithoutPrefix}.js`); + + // If the element is already defined, there's nothing more to do + if (customElements.get(tagName)) { + return Promise.resolve(); + } + + // Register it + return new Promise((resolve, reject) => { + import(path) + .then(() => resolve()) + .catch(() => reject(new Error(`Unable to automatically load<${tagName}> from ${path}`))); + }); +} + +// Initial discovery +discover(document.body); + +// Listen for new undefined elements +observer.observe(document.body, { subtree: true, childList: true }); diff --git a/tsconfig.json b/tsconfig.json index ec49f88b..49de6876 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ "target": "es2017", + "module": "es2020", "lib": [ "dom", "dom.Iterable",