Skip to content

Commit

Permalink
Feat/auto loader (#140)
Browse files Browse the repository at this point in the history
* feat: prerelease autoloader

* feat: prerelease autoloader

* feat: make autoloader covers shadow dom

* feat: make autoloader covers shadow dom

* feat: autoloader for banana
  • Loading branch information
FriedRiceNoodles authored Jun 3, 2024
1 parent c433c37 commit 4bb6fbb
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .changeset/tender-pianos-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@banana-ui/banana': minor
'@banana-ui/react': minor
---

Autoloader for banana.
88 changes: 88 additions & 0 deletions packages/banana/src/banana-autoloader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Inpired by https://github.com/shoelace-style/shoelace/blob/next/src/shoelace-autoloader.ts

function getBasePath(subpath = '') {
const scripts = [...document.getElementsByTagName('script')] as HTMLScriptElement[];
const autoloader = scripts.find((script) => script.src.includes('banana-autoloader')) as HTMLScriptElement;
const basePath = autoloader.src.split('/').slice(0, -1).join('/');

// Return the base path without a trailing slash. If one exists, append the subpath separated by a slash.
return basePath.replace(/\/$/, '') + (subpath ? `/${subpath.replace(/^\//, '')}` : ``);
}

const observer = new MutationObserver((mutations) => {
for (const { addedNodes } of mutations) {
for (const node of addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
void discover(node as Element);
}
}
}
});

/**
* Checks a node for undefined elements and attempts to register them.
*/
async function discover(root: Element | ShadowRoot) {
if (!root) return;

const rootTagName = root instanceof Element ? root.tagName.toLowerCase() : '';
const rootIsBananaElement = rootTagName?.toLowerCase().startsWith('b-');
const rootIsCustomElement = rootTagName?.includes('-');

const tags = [...root.querySelectorAll(':not(:defined)')]
.map((el) => el.tagName.toLowerCase())
.filter((tag) => tag.startsWith('b-'));

// If the root element is an undefined Banana component, add it to the list
if (rootIsBananaElement && !customElements.get(rootTagName)) {
tags.push(rootTagName);
}

// Make the list unique
const tagsToRegister = [...new Set(tags)];

const notBananaCustomElements = [...root.querySelectorAll('*')].filter(
(el) => el.tagName.includes('-') && !el.tagName.toLowerCase().startsWith('b-'),
);

// If the root element is a custom element and not a Banana component, add it to the list
if (rootIsCustomElement && !rootIsBananaElement && root instanceof Element) {
notBananaCustomElements.push(root);
}

// Discover any shadow roots
const customElementsPromises = notBananaCustomElements.map((el) => {
return customElements.whenDefined(el.tagName);
});
void Promise.allSettled(customElementsPromises).then(() => {
notBananaCustomElements.forEach((el) => {
if (el.shadowRoot) void discover(el.shadowRoot);
});
});

await Promise.allSettled(tagsToRegister.map((tagName) => register(tagName)));
}

/**
* Registers an element by tag name.
*/
function register(tagName: string): Promise<void> {
// If the element is already defined, there's nothing more to do
if (customElements.get(tagName)) {
return Promise.resolve();
}

const tagWithoutPrefix = tagName.replace(/^b-/i, '');
const path = getBasePath(`${tagWithoutPrefix}/index.js`);

// Register it
return new Promise((resolve, reject) => {
import(path).then(() => resolve()).catch(() => reject(new Error(`Unable to autoload <${tagName}> from ${path}`)));
});
}

// Initial discovery
void discover(document.body);

// Listen for new undefined elements
observer.observe(document.documentElement, { subtree: true, childList: true });
22 changes: 21 additions & 1 deletion rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const UMDName = process
.pop()
.replace(/(^|-)(\w)/g, (_, _$1, $2) => $2.toUpperCase());

export default [
const rollupConfig = [
{
input: './src/index.ts',
output: {
Expand Down Expand Up @@ -105,3 +105,23 @@ export default [
],
},
];

// 如果存在./src/autoloader.ts,则添加到rollupConfig中
if (fs.existsSync('./src/banana-autoloader.ts')) {
rollupConfig.push({
input: './src/banana-autoloader.ts',
output: {
dir: 'dist',
format: 'es',
entryFileNames: 'banana-autoloader.js',
},
plugins: [
typescript(),
nodeResolve({
extensions: ['.ts', '.js'],
}),
],
});
}

export default rollupConfig;
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"experimentalDecorators": true,
"useDefineForClassFields": false,
"jsx": "react-jsx",
"module": "esnext",
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
Expand Down

0 comments on commit 4bb6fbb

Please sign in to comment.