├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── .gitignore ├── .npmrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── Procfile.dev ├── README.md ├── docs ├── .algolia │ └── config.json ├── .gitignore ├── README.md ├── composables.d.ts ├── cypress.config.js ├── cypress │ ├── e2e │ │ ├── dark-mode.cypress.cy.js │ │ ├── docsearch.cypress.cy.js │ │ ├── helpers.js │ │ ├── home.cypress.cy.js │ │ └── sidebar.cypress.cy.js │ ├── fixtures │ │ └── example.json │ └── support │ │ ├── commands.js │ │ └── e2e.js ├── icons │ ├── arrowRight.svg │ ├── astro.svg │ ├── cloudflare.svg │ ├── logo.svg │ ├── netlify.svg │ ├── text.svg │ ├── vercel.svg │ ├── vite.svg │ └── vue.svg ├── iles.config.ts ├── images │ ├── banner.avif │ ├── banner.jpg │ ├── banner.png │ ├── devtools-frameworks.png │ ├── favicon.ico │ ├── logo-round.png │ ├── logo.png │ ├── soon.jpg │ └── stackblitz.svg ├── index.d.ts ├── modules │ └── lastUpdated.ts ├── package.json ├── postcss.config.js ├── public │ ├── _headers │ ├── _redirects │ ├── apple-touch-icon.png │ ├── favicon.svg │ ├── pwa-192x192.png │ ├── pwa-512x512.png │ └── robots.txt ├── scripts │ └── test-cypress ├── src │ ├── app.ts │ ├── components │ │ ├── AppButton.vue │ │ ├── AstroLogo.vue │ │ ├── AutoImported.vue │ │ ├── Caption.vue │ │ ├── CloudflareLogo.vue │ │ ├── DarkModeSwitch.vue │ │ ├── DocSearch.tsx │ │ ├── EditLink.vue │ │ ├── GitHubLogo.vue │ │ ├── HomeFeatures.vue │ │ ├── HomeHero.vue │ │ ├── Iles.vue │ │ ├── Image.vue │ │ ├── LastUpdated.vue │ │ ├── Logo.vue │ │ ├── MainContainer.vue │ │ ├── MetaTags.vue │ │ ├── NavBarButton.vue │ │ ├── NavBarLink.vue │ │ ├── NavBarLinks.vue │ │ ├── NavBarLogo.vue │ │ ├── NetlifyLogo.vue │ │ ├── NextAndPrevLinks.vue │ │ ├── OutboundLink.vue │ │ ├── PageFooter.vue │ │ ├── PageHooks.mdx │ │ ├── ReloadPrompt.vue │ │ ├── SearchButton.vue │ │ ├── SidebarBackground.vue │ │ ├── SidebarHeader.vue │ │ ├── SidebarLink.vue │ │ ├── SidebarLinkItem.vue │ │ ├── SidebarNav.vue │ │ ├── SidebarToggle.vue │ │ ├── TableOfContents.vue │ │ ├── TheFooter.vue │ │ ├── TheNavBar.vue │ │ ├── TheRightSidebar.vue │ │ ├── TheSidebar.vue │ │ ├── TimeAgo.vue │ │ ├── Tip.vue │ │ ├── VercelLogo.vue │ │ ├── ViteLogo.vue │ │ ├── VueLogo.vue │ │ ├── contact.mdx │ │ └── should.mdx │ ├── layouts │ │ ├── base.vue │ │ ├── default.vue │ │ └── home.vue │ ├── logic │ │ ├── config.ts │ │ ├── dark-color-scheme-check.ts │ │ ├── dark.ts │ │ ├── sidebar.ts │ │ └── utils.ts │ ├── pages │ │ ├── 404.vue │ │ ├── config │ │ │ └── index.mdx │ │ ├── dynamic │ │ │ └── [section].vue │ │ ├── faqs │ │ │ ├── index.mdx │ │ │ └── troubleshooting.mdx │ │ ├── guide │ │ │ ├── client-scripts.mdx │ │ │ ├── deployment.mdx │ │ │ ├── development.mdx │ │ │ ├── documents.mdx │ │ │ ├── frameworks.mdx │ │ │ ├── hydration.mdx │ │ │ ├── index.mdx │ │ │ ├── introduction.mdx │ │ │ ├── markdown.mdx │ │ │ ├── meta-tags.mdx │ │ │ ├── overview.mdx │ │ │ ├── plugins.mdx │ │ │ ├── pwa.mdx │ │ │ ├── routing.mdx │ │ │ ├── rss.mdx │ │ │ └── turbo.mdx │ │ └── index.mdx │ ├── site.ts │ ├── style.css │ └── styles │ │ ├── all.css │ │ ├── docsearch.css │ │ ├── global.css │ │ ├── highlight.css │ │ ├── navbar.css │ │ ├── prose.css │ │ ├── themes.css │ │ ├── utilities.css │ │ └── variables.css ├── tsconfig.json └── uno.config.ts ├── eslint.config.mjs ├── iles.config.ts ├── netlify.toml ├── nx.json ├── package.json ├── packages ├── excerpt │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── excerpt.cjs │ │ ├── excerpt.ts │ │ ├── recma-plugin.ts │ │ ├── rehype-plugin.ts │ │ └── types.ts │ └── tsup.config.ts ├── feed │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── feed.cjs │ │ ├── feed.ts │ │ ├── render-feed.ts │ │ └── types.ts │ └── tsup.config.ts ├── headings │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── headings.cjs │ │ └── headings.ts │ └── tsup.config.ts ├── hydration │ ├── CHANGELOG.md │ ├── README.md │ ├── hydration.ts │ ├── package.json │ ├── preact.ts │ ├── solid.ts │ ├── svelte.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── types.ts │ ├── vanilla.ts │ └── vue.ts ├── icons │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── icons.mjs │ │ └── icons.ts │ └── tsup.config.ts ├── iles │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── CHANGELOG.md │ ├── README.md │ ├── bin │ │ └── iles.js │ ├── config.js │ ├── index.cjs │ ├── jsx-dev-runtime.js │ ├── jsx-runtime.js │ ├── package.json │ ├── scripts │ │ ├── copyClient.js │ │ ├── copyShared.js │ │ └── watchAndCopy.js │ ├── src │ │ ├── client │ │ │ ├── app │ │ │ │ ├── components │ │ │ │ │ ├── App.vue │ │ │ │ │ ├── DebugPanel.vue │ │ │ │ │ ├── Island.vue │ │ │ │ │ └── NotFound.vue │ │ │ │ ├── composables │ │ │ │ │ ├── appConfig.ts │ │ │ │ │ ├── devtools.ts │ │ │ │ │ ├── islandDefinitions.ts │ │ │ │ │ ├── mdxComponents.ts │ │ │ │ │ ├── pageData.ts │ │ │ │ │ ├── reactivity.ts │ │ │ │ │ ├── renderer.ts │ │ │ │ │ ├── routerLinks.ts │ │ │ │ │ └── vueRenderer.ts │ │ │ │ ├── head.ts │ │ │ │ ├── hydration.ts │ │ │ │ ├── index.ts │ │ │ │ ├── layout.ts │ │ │ │ ├── props.ts │ │ │ │ └── utils.ts │ │ │ ├── client.d.ts │ │ │ ├── index.ts │ │ │ ├── tsconfig.json │ │ │ └── virtual.d.ts │ │ ├── node │ │ │ ├── alias.ts │ │ │ ├── build │ │ │ │ ├── build.ts │ │ │ │ ├── bundle.ts │ │ │ │ ├── chunks.ts │ │ │ │ ├── islands.ts │ │ │ │ ├── rebaseImports.ts │ │ │ │ ├── render.ts │ │ │ │ ├── routes.ts │ │ │ │ ├── sitemap.ts │ │ │ │ ├── utils.ts │ │ │ │ └── write.ts │ │ │ ├── cli.ts │ │ │ ├── config.ts │ │ │ ├── constants.ts │ │ │ ├── index.ts │ │ │ ├── modules.ts │ │ │ ├── plugin │ │ │ │ ├── composables.ts │ │ │ │ ├── documents.ts │ │ │ │ ├── hmr.ts │ │ │ │ ├── markdown.ts │ │ │ │ ├── middleware.ts │ │ │ │ ├── parse.ts │ │ │ │ ├── plugin.ts │ │ │ │ ├── remarkWrapIslands.ts │ │ │ │ ├── site.ts │ │ │ │ ├── utils.ts │ │ │ │ └── wrap.ts │ │ │ ├── preview.ts │ │ │ ├── publicUtils.ts │ │ │ ├── server.ts │ │ │ └── utils.ts │ │ └── shared │ │ │ ├── shared.ts │ │ │ └── tsconfig.json │ ├── tests │ │ ├── __snapshots__ │ │ │ ├── build.spec.ts.snap │ │ │ └── site.spec.ts.snap │ │ ├── app-config.spec.ts │ │ ├── build.spec.ts │ │ ├── exports.spec.ts │ │ ├── layouts.spec.ts │ │ ├── not-found.spec.ts │ │ ├── parse.spec.ts │ │ ├── pretty-urls.spec.ts │ │ ├── resolvers.spec.ts │ │ ├── routes.spec.ts │ │ ├── site.spec.ts │ │ ├── user-app.spec.ts │ │ └── utils.spec.ts │ ├── tsconfig.json │ ├── tsup-cjs.config.ts │ ├── tsup.config.ts │ ├── turbo.js │ └── types │ │ ├── client.d.ts │ │ ├── index.d.ts │ │ ├── shared.d.ts │ │ └── virtual.d.ts ├── images │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Picture.vue │ │ └── images.ts │ └── tsup.config.ts ├── mdx │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── mdx-vite-plugins.ts │ │ ├── mdx.cjs │ │ ├── mdx.ts │ │ ├── plugins.ts │ │ ├── recma-plugin.ts │ │ ├── rehype-raw-expressions.ts │ │ ├── remark-internal-hrefs.ts │ │ ├── remark-mdx-images.ts │ │ ├── types.ts │ │ └── utils.ts │ └── tsup.config.ts ├── pages │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── api.ts │ │ ├── frontmatter.ts │ │ ├── hmr.ts │ │ ├── pages.cjs │ │ ├── pages.ts │ │ ├── types.ts │ │ └── utils.ts │ └── tsup.config.ts ├── prerender │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── prerender.ts │ ├── svelte.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── prism │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── prism.cjs │ │ └── prism.ts │ └── tsup.config.ts └── pwa │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── pwa.mjs │ └── pwa.ts │ └── tsup.config.ts ├── playground └── the-vue-point │ ├── .gitignore │ ├── README.md │ ├── composables.d.ts │ ├── iles.config.ts │ ├── index.d.ts │ ├── package.json │ ├── public │ ├── _headers │ ├── favicon.ico │ ├── iles.svg │ └── logo.svg │ ├── src │ ├── app.ts │ ├── components │ │ ├── Author.vue │ │ ├── BackLink.tsx │ │ ├── MetaTags.vue │ │ ├── NavBarLinks.svelte │ │ └── PostDate.vue │ ├── images │ │ ├── bench.png │ │ └── one-piece.png │ ├── layouts │ │ ├── default.vue │ │ └── post.vue │ ├── logic │ │ ├── pagination.ts │ │ └── posts.ts │ ├── pages │ │ ├── 404.vue │ │ ├── feed.vue │ │ ├── index.vue │ │ └── posts │ │ │ ├── [page].vue │ │ │ ├── hello-2021.mdx │ │ │ ├── volar-1-0.mdx │ │ │ ├── vue-2-7-beta.mdx │ │ │ ├── vue-2-7-naruto.mdx │ │ │ ├── vue-3-2.mdx │ │ │ ├── vue-3-as-the-new-default.mdx │ │ │ └── vue-3-one-piece.mdx │ ├── site.ts │ └── style.css │ ├── tsconfig.json │ └── uno.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── changelog.ts └── release.ts └── tsconfig.json /.gitattributes: -------------------------------------------------------------------------------- 1 | *.jpg filter=lfs diff=lfs merge=lfs -text 2 | *.png filter=lfs diff=lfs merge=lfs -text 3 | *.webp filter=lfs diff=lfs merge=lfs -text 4 | *.avif filter=lfs diff=lfs merge=lfs -text 5 | *.jpeg filter=lfs diff=lfs merge=lfs -text 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve. Please check the Troubleshooting section before opening an issue. 4 | title: '' 5 | labels: 'bug: pending triage' 6 | assignees: '' 7 | --- 8 | 9 | [troubleshooting section]: https://iles-docs.netlify.app/faqs/troubleshooting 10 | 11 | - [ ] I have read the __[troubleshooting section]__ before opening an issue. 12 | - [ ] I have tried upgrading `iles` and `vite`. 13 | 14 | ### Description 📖 15 | 16 | _Provide a clear and concise description of what the bug is._ 17 | 18 | ### Reproduction 🐞 19 | 20 | _Please provide a link to a repo that can reproduce the problem you ran into._ 21 | 22 |
23 | Dependencies Info 24 | 25 | _Run `npx iles info` and `pnpm list` (or `npm list`) and provide the output:_ 26 | 27 | ``` 28 | 29 | ``` 30 |
31 | 32 | ### Logs 📜 33 | 34 | _If not providing a reproduction:_ 35 | 36 |
37 | Output 38 | 39 | _Run `DEBUG=iles:* npm run dev` or `DEBUG=iles:* npm run build` and provide the output:_ 40 | 41 | ``` 42 | 43 | ``` 44 |
45 | 46 | ### Screenshots 📷 47 | 48 | _Provide console or browser screenshots of the problem_. 49 | 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Troubleshooting & FAQs 4 | url: https://iles-docs.netlify.app/faqs/troubleshooting 5 | about: 'Please check the most common configuration problems before opening an issue.' 6 | - name: Discord Chat 7 | url: https://discord.com/channels/804011606160703521/900707742203920394 8 | about: 'Discuss with other users in the #iles channel of the Vite.js Discord server.' 9 | - name: Questions & Discussions 10 | url: https://github.com/ElMassimo/iles/discussions 11 | about: Use GitHub discussions for questions and discussions. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 📖 2 | 3 | This pull request 4 | 5 | ### Background 📜 6 | 7 | This was happening because 8 | 9 | ### The Fix 🔨 10 | 11 | By changing 12 | 13 | ### Screenshots 📷 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | .iles-ssg-temp/ 4 | .vite-islands-temp/ 5 | .DS_Store 6 | *.local 7 | *.tgz 8 | .pnpm-debug.log 9 | packages/.pnpm-debug.log 10 | components.d.ts 11 | composables.d.ts 12 | 13 | packages/iles/tests/renderAsync.ts 14 | 15 | .idea/ 16 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | ignore-workspace-root-check=true 3 | strict-peer-dependencies=false 4 | auto-install-peers=true 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "antfu.unocss" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "files.exclude": { 4 | "**/dist": true 5 | }, 6 | "files.watcherExclude": { 7 | "**/dist/**": true 8 | }, 9 | "search.exclude": { 10 | "**/dist": true 11 | } 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Máximo Mussini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | packages: npm run dev:packages 2 | 3 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .vitepress/metadata.json 4 | cypress/videos 5 | cypress/screenshots 6 | 7 | # Algolia 8 | .algolia.env 9 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | îles — french word for "islands" 9 |

10 | 11 |

Islands of interactivity with Vue in Vite.js

12 | 13 |

14 | 15 | 16 | 17 | 18 | License 19 | 20 |

21 | 22 |
23 | 24 | [docs]: https://iles-docs.netlify.app 25 | [îles]: https://iles-docs.netlify.app 26 | 27 | # Documentation 📖 28 | 29 | This is the source code of the [documentation website for îles][docs]. 30 | 31 | It's built using [îles]. 32 | 33 | [__Live Website__][docs] 34 | -------------------------------------------------------------------------------- /docs/composables.d.ts: -------------------------------------------------------------------------------- 1 | // generated by iles 2 | // We suggest you to commit this file into source control 3 | 4 | declare global { 5 | const definePageComponent: typeof import('iles')['definePageComponent'] 6 | const useDocuments: typeof import('iles')['useDocuments'] 7 | const useHead: typeof import('iles')['useHead'] 8 | const usePage: typeof import('iles')['usePage'] 9 | const useRoute: typeof import('iles')['useRoute'] 10 | } 11 | 12 | export { } 13 | -------------------------------------------------------------------------------- /docs/cypress.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | viewportWidth: 1280, 3 | chromeWebSecurity: false, 4 | retries: { 5 | runMode: 4, 6 | }, 7 | e2e: { 8 | baseUrl: 'http://localhost:3050', 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /docs/cypress/e2e/dark-mode.cypress.cy.js: -------------------------------------------------------------------------------- 1 | import { visitHome, navigateTo, goBackHome, assertPage } from './helpers' 2 | 3 | describe('Dark Mode', () => { 4 | const toggleTheme = () => { 5 | cy.get(`[aria-label="Toggle theme"]`).click() 6 | } 7 | 8 | const assertTheme = (theme) => 9 | cy.get('html').then((html) => 10 | expect(html.hasClass('dark')).to.equal(theme === 'dark')) 11 | 12 | it('can toggle on and off', () => { 13 | visitHome() 14 | cy.get('html').then((html) => { 15 | if (html.hasClass('dark')) toggleTheme() 16 | assertTheme('light') 17 | }) 18 | 19 | toggleTheme() 20 | assertTheme('dark') 21 | 22 | // Ensure Turbo hydrates after navigation. 23 | navigateTo('Install') 24 | assertPage({ title: 'Getting Started' }) 25 | assertTheme('dark') 26 | 27 | toggleTheme() 28 | assertTheme('light') 29 | 30 | goBackHome() 31 | 32 | toggleTheme() 33 | assertTheme('dark') 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /docs/cypress/e2e/docsearch.cypress.cy.js: -------------------------------------------------------------------------------- 1 | import { visitHome, navigateTo, goBackHome, assertPage } from './helpers' 2 | 3 | describe('DocSearch', () => { 4 | const openSearchModal = () => { 5 | cy.get(`.nav-bar-button[aria-label="Search"]`).click() 6 | searchModal().should('be.visible') 7 | } 8 | 9 | const openSearchModalWithKeyboard = () => { 10 | cy.wait(100) // Give Turbo time to replace the body. 11 | cy.get('body').type('{cmd+k}') 12 | searchModal().should('be.visible') 13 | } 14 | 15 | const searchModal = () => 16 | cy.get('.DocSearch-Modal') 17 | 18 | const closeSearchModal = () => { 19 | cy.get('body').type('{esc}') 20 | searchModal().should('not.exist') 21 | } 22 | 23 | it('can open by clicking or with keystroke', () => { 24 | visitHome() 25 | openSearchModal() 26 | closeSearchModal() 27 | 28 | openSearchModalWithKeyboard() 29 | closeSearchModal() 30 | 31 | // Ensure Turbo reactivates the islands. 32 | navigateTo('FAQs') 33 | assertPage({ title: 'FAQs' }) 34 | 35 | openSearchModal() 36 | closeSearchModal() 37 | 38 | goBackHome() 39 | openSearchModalWithKeyboard() 40 | closeSearchModal() 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /docs/cypress/e2e/helpers.js: -------------------------------------------------------------------------------- 1 | export const assertPage = ({ title, content }) => { 2 | cy.get('h1').should('contain', title) 3 | if (content) cy.get('p').should('contain', content) 4 | } 5 | 6 | export const navigateTo = (title) => { 7 | cy.get('a').contains(title).click() 8 | waitForHydration() 9 | } 10 | 11 | export const visit = (path) => { 12 | cy.visit(path) 13 | waitForHydration() 14 | } 15 | 16 | export const visitHome = () => 17 | visit('/') 18 | 19 | export const goBackHome = () => { 20 | cy.go('back') 21 | assertPage({ title: 'îles' }) 22 | waitForHydration() 23 | } 24 | 25 | // Wait until the relevant islands are hydrated. 26 | export const waitForHydration = () => { 27 | cy.wait(100) 28 | cy.get('#ile-1[hydrated]').should('have.length', 1) 29 | cy.get('#ile-2[hydrated]').should('have.length', 1) 30 | cy.get('#ile-3[hydrated]').should('have.length', 1) 31 | } 32 | -------------------------------------------------------------------------------- /docs/cypress/e2e/home.cypress.cy.js: -------------------------------------------------------------------------------- 1 | import { visitHome, navigateTo, assertPage, goBackHome } from './helpers' 2 | 3 | describe('The Home Page', () => { 4 | it('successfully loads', () => { 5 | visitHome() 6 | assertPage({ title: 'îles', content: 'The Joyful Site Generator' }) 7 | cy.get('section') 8 | .should('contain', 'Partial Hydration') 9 | .should('contain', 'Ship JS only for the interactive bits') 10 | 11 | navigateTo('Get Started') 12 | assertPage({ title: 'Introduction' }) 13 | cy.get('aside').should('contain', '🧱 Layouts and Components') 14 | cy.get('blockquote').should('contain', 'Project Status: Beta') 15 | 16 | goBackHome() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /docs/cypress/e2e/sidebar.cypress.cy.js: -------------------------------------------------------------------------------- 1 | import { visit, navigateTo, goBackHome, assertPage, waitForHydration } from './helpers' 2 | 3 | describe('Sidebar Toggle', () => { 4 | const sidebar = () => 5 | cy.get('#sidebar-panel') 6 | 7 | const sidebarToggle = () => 8 | cy.get(`button[aria-label="Toggle Sidebar"]`) 9 | 10 | const openSidebar = () => { 11 | sidebarToggle().click() 12 | sidebar().should('be.visible') 13 | } 14 | 15 | const closeSidebar = () => { 16 | cy.get(`button[aria-label="Close Sidebar"]`).click() 17 | sidebar().should('not.be.visible') 18 | } 19 | 20 | it('is always open in mobile', () => { 21 | visitHome() 22 | visit('/guide/markdown') 23 | sidebar().should('be.visible') 24 | sidebarToggle().should('not.be.visible') 25 | }) 26 | 27 | test.skipIf(process.env.CI)('can open in mobile', () => { 28 | visitHome() 29 | cy.viewport(500, 720) 30 | visit('/guide/frameworks') 31 | openSidebar() 32 | closeSidebar() 33 | 34 | // Ensure Turbo reactivates the islands. 35 | openSidebar() 36 | sidebar().contains('Config').click() 37 | 38 | // Give Turbo time to replace the body. 39 | waitForHydration() 40 | assertPage({ title: 'Config' }) 41 | sidebar().should('not.be.visible') 42 | 43 | openSidebar() 44 | closeSidebar() 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /docs/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /docs/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /docs/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /docs/icons/arrowRight.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/icons/astro.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/icons/cloudflare.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/icons/vercel.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/icons/vite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/icons/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/images/banner.avif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c55b939c3b59ea409fd3b51b175d71d78563f6dafd4dc00b2951a66dfbaa72b2 3 | size 58364 4 | -------------------------------------------------------------------------------- /docs/images/banner.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:6fb44379216715de7b4d6386877bd889cbb5ee454d03a539ccdcb85b0c132f97 3 | size 73505 4 | -------------------------------------------------------------------------------- /docs/images/banner.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:136b767e600483a9ede8bfb89055d057aaa4f7c2bba8bde74b14ba5fb7d1946e 3 | size 845704 4 | -------------------------------------------------------------------------------- /docs/images/devtools-frameworks.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:84fed464d406afd2f56dc36d4c59138d76dca403b2752b0da108b603fc63ac57 3 | size 14296 4 | -------------------------------------------------------------------------------- /docs/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElMassimo/iles/c9e95d729013a8f8f425e894cc7785b954cbcd85/docs/images/favicon.ico -------------------------------------------------------------------------------- /docs/images/logo-round.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:dae764870f9e01a99eb623f9efdf7c1a4463e8a1575a9323ac5f597be08cce0d 3 | size 37325 4 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:dc875bb06665f2bd26d28fcd2775dcd053cbae198263e73c9d51eab893f63780 3 | size 31168 4 | -------------------------------------------------------------------------------- /docs/images/soon.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bb2f7d5efaa2de07130d58aa183d35700b48214ecaf91c246ba37df1295602d6 3 | size 35123 4 | -------------------------------------------------------------------------------- /docs/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /docs/modules/lastUpdated.ts: -------------------------------------------------------------------------------- 1 | import { sync as spawn } from 'cross-spawn' 2 | import type { IlesModule } from 'iles' 3 | 4 | export default () => ({ 5 | name: 'git-last-updated-at', 6 | extendFrontmatter (frontmatter, filename) { 7 | const lastUpdated = lastUpdatedFromGit(filename) 8 | if (lastUpdated) 9 | frontmatter.meta.lastUpdated = lastUpdated 10 | }, 11 | }) as IlesModule 12 | 13 | function lastUpdatedFromGit (filename: string) { 14 | try { 15 | const result = spawn('git', ['log', '-1', '--format=%at', filename]) 16 | const date = new Date(parseInt(result.stdout as any) * 1000) 17 | return isNaN(Number(date)) ? null : date 18 | } 19 | catch { 20 | return null 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "iles --open --port 3001", 8 | "build": "iles build", 9 | "preview": "iles preview --open --port 3050", 10 | "now": "npm run build && npm run preview", 11 | "lint": "eslint .", 12 | "lint:fix": "eslint . --fix", 13 | "check": "vue-tsc --noEmit", 14 | "cy:run": "scripts/test-cypress" 15 | }, 16 | "engines": { 17 | "node": "^14.18 || >= 16.0.0" 18 | }, 19 | "devDependencies": { 20 | "@iconify-json/bx": "^1.1.10", 21 | "@iconify-json/heroicons-outline": "^1.1.10", 22 | "@islands/headings": "workspace:*", 23 | "@islands/icons": "workspace:*", 24 | "@islands/prism": "workspace:*", 25 | "@islands/pwa": "workspace:*", 26 | "@preact/preset-vite": "^2.9.0", 27 | "@types/cross-spawn": "^6.0.6", 28 | "@vueuse/core": "^10.11.0", 29 | "@vue-macros/reactivity-transform": "^1.0.4", 30 | "autoprefixer": "^10.4.19", 31 | "cross-spawn": "^7.0.3", 32 | "iles": "workspace:*", 33 | "postcss-nesting": "^12.1.5", 34 | "preact": "^10.24.3", 35 | "preact-render-to-string": "^6.5.11", 36 | "rehype-external-links": "^3.0.0", 37 | "unocss": "^0.61.8", 38 | "vite-plugin-inspect": "^0.8.7", 39 | "vue-tsc": "^2.1.10" 40 | }, 41 | "dependencies": { 42 | "@docsearch/css": "^3.6.1", 43 | "@mussi/docsearch": "3.1.2-beta.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | autoprefixer: {}, 4 | 'postcss-nesting': {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /docs/public/_headers: -------------------------------------------------------------------------------- 1 | / 2 | x-frame-options: DENY 3 | x-xss-protection: 1; mode=block 4 | 5 | /faqs/* 6 | x-frame-options: DENY 7 | x-xss-protection: 1; mode=block 8 | 9 | /guide/* 10 | x-frame-options: DENY 11 | x-xss-protection: 1; mode=block 12 | 13 | /*.html 14 | x-frame-options: DENY 15 | x-xss-protection: 1; mode=block 16 | 17 | /assets/* 18 | cache-control: max-age=31536000 19 | cache-control: immutable 20 | 21 | /logo.svg 22 | cache-control: max-age=31536000 23 | cache-control: immutable 24 | 25 | /favicon.ico 26 | cache-control: max-age=31536000 27 | cache-control: immutable 28 | 29 | /* 30 | x-content-type-options: nosniff 31 | referrer-policy: no-referrer 32 | strict-transport-security: max-age=31536000; includeSubDomains 33 | content-security-policy-report-only: default-src 'self'; font-src 'self'; img-src 'self' data: developer.stackblitz.com; script-src 'self' 'unsafe-inline'; connect-src 'self' 'unsafe-inline'; style-src 'self' 34 | -------------------------------------------------------------------------------- /docs/public/_redirects: -------------------------------------------------------------------------------- 1 | /config/plugins https://iles-docs.netlify.app/guide/plugins 301 2 | /guide/comparisons https://iles-docs.netlify.app/faqs 301 3 | -------------------------------------------------------------------------------- /docs/public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3aabe05feb65df10e43dd18353401ae2fc395823bd029f95a1c8ed806b45220f 3 | size 5099 4 | -------------------------------------------------------------------------------- /docs/public/pwa-192x192.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a68ec9fbc02330633e4b62812ad01f9f588e4d08d6f66ea68b830efb646b47e1 3 | size 5455 4 | -------------------------------------------------------------------------------- /docs/public/pwa-512x512.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cca23b5c3541651b928085f740977ce7c16633a7fdd62e72ceb23c0e90b4cbfb 3 | size 13203 4 | -------------------------------------------------------------------------------- /docs/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /docs/scripts/test-cypress: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # scripts/test-cypress: Starts the local server and runs Cypress tests. 4 | set -e 5 | cd "$(dirname "$0")/.." 6 | 7 | echo '== Starting server ==' 8 | pnpm run preview & 9 | 10 | echo '== Waiting for server to respond... ==' 11 | pnpx wait-on http-get://localhost:3050 --delay 1000 --timeout 15000 12 | 13 | echo '== Server ready! ==' 14 | 15 | pnpx cypress@10.6.0 run && echo '== Finished! ==' 16 | -------------------------------------------------------------------------------- /docs/src/app.ts: -------------------------------------------------------------------------------- 1 | import 'virtual:uno.css' 2 | import '@unocss/reset/tailwind-compat.css' 3 | import '~/styles/all.css' 4 | 5 | import { defineApp } from 'iles' 6 | 7 | import Image from '~/components/Image.vue' 8 | 9 | import checkDarkTheme from '~/logic/dark-color-scheme-check?raw' 10 | import type { Script } from '@unhead/schema' 11 | 12 | type TurboScript = Script & { once: true } 13 | 14 | export default defineApp({ 15 | head: { 16 | htmlAttrs: { lang: 'en-US' }, 17 | script: [ 18 | { children: checkDarkTheme, once: true } as TurboScript, 19 | ], 20 | }, 21 | mdxComponents: { 22 | img: Image, 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /docs/src/components/AppButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 79 | -------------------------------------------------------------------------------- /docs/src/components/AstroLogo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/AutoImported.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/Caption.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/src/components/CloudflareLogo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/DarkModeSwitch.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | 14 | 33 | -------------------------------------------------------------------------------- /docs/src/components/DocSearch.tsx: -------------------------------------------------------------------------------- 1 | import { DocSearch, DocSearchProps } from '@mussi/docsearch' 2 | import '~/styles/docsearch.css' 3 | 4 | const options: Partial = { 5 | transformItems (items) { 6 | return items.map((item) => { 7 | const getRelativePath = (url: string) => { 8 | const { pathname, hash } = new URL(url) 9 | return pathname + hash 10 | } 11 | return Object.assign({}, item, { url: getRelativePath(item.url) }) 12 | }) 13 | }, 14 | } 15 | 16 | const IlesDocSearch = (props: Partial) => 17 | 24 | 25 | export default IlesDocSearch 26 | -------------------------------------------------------------------------------- /docs/src/components/EditLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /docs/src/components/GitHubLogo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/HomeFeatures.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /docs/src/components/HomeHero.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 55 | -------------------------------------------------------------------------------- /docs/src/components/Iles.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /docs/src/components/Image.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /docs/src/components/LastUpdated.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /docs/src/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/MainContainer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 37 | -------------------------------------------------------------------------------- /docs/src/components/MetaTags.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | -------------------------------------------------------------------------------- /docs/src/components/NavBarButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /docs/src/components/NavBarLink.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 25 | -------------------------------------------------------------------------------- /docs/src/components/NavBarLinks.vue: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /docs/src/components/NavBarLogo.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /docs/src/components/NetlifyLogo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/NextAndPrevLinks.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | 35 | -------------------------------------------------------------------------------- /docs/src/components/OutboundLink.vue: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /docs/src/components/PageFooter.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /docs/src/components/PageHooks.mdx: -------------------------------------------------------------------------------- 1 | [extendFrontmatter]: /guide/plugins#islandspages 2 | [extendRoute]: /guide/plugins#islandspages 3 | 4 | 5 | You can customize all pages using the [extendFrontmatter] and [extendRoute] hooks. 6 | 7 | -------------------------------------------------------------------------------- /docs/src/components/ReloadPrompt.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 33 | 34 | 47 | -------------------------------------------------------------------------------- /docs/src/components/SearchButton.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /docs/src/components/SidebarBackground.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /docs/src/components/SidebarHeader.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /docs/src/components/SidebarLink.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /docs/src/components/SidebarLinkItem.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 33 | 34 | 68 | -------------------------------------------------------------------------------- /docs/src/components/SidebarNav.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /docs/src/components/SidebarToggle.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/TableOfContents.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 49 | -------------------------------------------------------------------------------- /docs/src/components/TheFooter.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | -------------------------------------------------------------------------------- /docs/src/components/TheNavBar.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 27 | -------------------------------------------------------------------------------- /docs/src/components/TheRightSidebar.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /docs/src/components/TimeAgo.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /docs/src/components/Tip.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /docs/src/components/VercelLogo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/ViteLogo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/VueLogo.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /docs/src/components/contact.mdx: -------------------------------------------------------------------------------- 1 | [twitter]: https://twitter.com/ilesjs 2 | [follow me]: https://twitter.com/MaximoMussini 3 | [GitHub Issues]: https://github.com/ElMassimo/iles/issues 4 | [GitHub Discussions]: https://github.com/ElMassimo/iles/discussions 5 | [project]: https://github.com/ElMassimo/iles 6 | 7 | ## Contact ✉️ 8 | 9 | Please visit [GitHub Issues] to report bugs you find, and [GitHub Discussions] to make feature requests, or to get help. 10 | 11 | Show some love by [⭐️ starring the project][project] if you find it useful! 12 | 13 | ## News 🗞 14 | 15 | [Follow me] or the [official îles account][twitter] on [Twitter]. 16 | -------------------------------------------------------------------------------- /docs/src/components/should.mdx: -------------------------------------------------------------------------------- 1 | [Vuepress]: https://v2.vuepress.vuejs.org/ 2 | [Jekyll]: https://github.com/ElMassimo/jekyll-vite 3 | [eleventy]: https://www.11ty.dev/ 4 | 5 | ## Should I use ? 6 | 7 | There are more mature tools that might be a better choice, such as [Vuepress]. 8 | 9 | The goal of this project is to combine the ease of use and development experience of 10 | building a site with Vue, while effortlessly shipping a zero JS site as if you 11 | were using [Jekyll] or [eleventy]. 12 | 13 | 14 | APIs may change on minor releases. Lock the version to avoid breakage. 15 | 16 | -------------------------------------------------------------------------------- /docs/src/layouts/base.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /docs/src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /docs/src/layouts/home.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /docs/src/logic/config.ts: -------------------------------------------------------------------------------- 1 | export interface SiteConfig { 2 | nav?: NavItem[] | false 3 | sidebar?: SideBarItem[] 4 | } 5 | 6 | // navbar -------------------------------------------------------------------- 7 | 8 | export type NavItem = NavItemWithLink | NavItemWithChildren 9 | 10 | export interface NavItemBase { 11 | text: string 12 | target?: string 13 | rel?: string 14 | ariaLabel?: string 15 | activeMatch?: string 16 | } 17 | 18 | export interface NavItemWithLink extends NavItemBase { 19 | link: string 20 | } 21 | 22 | export interface NavItemWithChildren extends NavItemBase { 23 | items: NavItemWithLink[] 24 | } 25 | 26 | // sidebar ------------------------------------------------------------------- 27 | 28 | export type SideBarItem = SideBarLink | SideBarGroup 29 | 30 | export interface SideBarLink { 31 | text: string 32 | link: string 33 | } 34 | 35 | export interface SideBarGroup extends SideBarLink { 36 | children: SideBarItem[] 37 | } 38 | -------------------------------------------------------------------------------- /docs/src/logic/dark-color-scheme-check.ts: -------------------------------------------------------------------------------- 1 | (() => { 2 | const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches 3 | const setting = localStorage.getItem('vueuse-color-scheme') || 'auto' 4 | if (setting === 'dark' || (prefersDark && setting !== 'light')) 5 | document.documentElement.classList.toggle('dark', true) 6 | })() 7 | -------------------------------------------------------------------------------- /docs/src/logic/dark.ts: -------------------------------------------------------------------------------- 1 | import { useDark, useToggle } from '@vueuse/core' 2 | 3 | export const isDark = useDark() 4 | export const toggleDark = useToggle(isDark) 5 | -------------------------------------------------------------------------------- /docs/src/logic/utils.ts: -------------------------------------------------------------------------------- 1 | const hashRE = /#.*$/ 2 | const extRE = /(index)?\.(md|html)$/ 3 | const endingSlashRE = /\/$/ 4 | 5 | /** 6 | * Remove `.md` or `.html` extention from the given path. It also converts 7 | * `index` to slush. 8 | */ 9 | export function normalize (path: string): string { 10 | return ensureStartingSlash(decodeURI(path).replace(hashRE, '').replace(extRE, '').replace(endingSlashRE, '')) 11 | } 12 | 13 | function ensureStartingSlash (path: string): string { 14 | return path.startsWith('/') ? path : `/${path}` 15 | } 16 | 17 | export function joinUrl (base: string, path: string): string { 18 | if (path.startsWith('#')) return path 19 | return `${base}${path.startsWith('/') ? path.slice(1) : path}` 20 | } 21 | -------------------------------------------------------------------------------- /docs/src/pages/404.vue: -------------------------------------------------------------------------------- 1 | 2 | title: Not Found 3 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /docs/src/pages/dynamic/[section].vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /docs/src/pages/faqs/troubleshooting.mdx: -------------------------------------------------------------------------------- 1 | [GitHub Issues]: https://github.com/ElMassimo/iles/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc 2 | [GitHub Discussions]: https://github.com/ElMassimo/iles/discussions 3 | [layout]: /guide/development#layouts 4 | [deployment]: /guide/deployment 5 | 6 | # Troubleshooting 7 | 8 | This section lists a few common gotchas, and bugs introduced in the past. 9 | 10 | Please skim through __before__ opening an [issue][GitHub Issues]. 11 | 12 | ## Page data not properly injected in app 13 | 14 | `usePage` can not be used inside islands, as it would cause the entire context 15 | (page, frontmatter, meta) to be included in the bundle. 16 | 17 | Instead, pass the necessary data explicitly as props. For example: 18 | 19 | ```vue 20 | 21 | ``` 22 | 23 | ## Could not load layout file during build 24 | 25 | [Layout file names must be lowercase][layout]: `default.vue` instead of `Default.vue`. 26 | 27 | Most [service providers][deployment] are Linux-based, which is case-sensitive. 28 | This is why it might work in your local macOS or Windows, but fail when [deploying][deployment]. 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/src/pages/guide/index.mdx: -------------------------------------------------------------------------------- 1 | [introduction]: /guide/introduction 2 | [configuration reference]: /config 3 | [starter]: https://github.com/ElMassimo/create-iles 4 | 5 | # Getting Started 6 | 7 | If you are interested in learning more about before trying it, read the [Introduction]. 8 | 9 | If you are looking for configuration options, visit the [configuration reference]. 10 | 11 | ## Try it Online ⚡️ 12 | 13 | import stackblitzSrc from '/images/stackblitz.svg' 14 | 15 |

16 | Try it on StackBlitz 17 |

18 | 19 | ## Installation 💿 20 | 21 | Run the following command to create a new îles project. 22 | 23 | ```bash 24 | pnpm create iles@next # or npm or yarn 25 | ``` 26 | 27 | The [starter] comes with Vue components and examples. Once you have installed 28 | all dependencies, give it a try by running npm run dev to start the 29 | development server. 30 | 31 | Alternatively, add `iles` to your `package.json` in an existing project. 32 | 33 | ## Usage 🚀 34 | 35 | The `iles` executable provides the following commands: 36 | 37 | - iles dev: Starts the development server 38 | - iles build: Creates a production build of the site 39 | - iles preview: Preview the site after building 40 | 41 | The following shortcuts are added by default when using the [starter]: 42 | 43 | ```json 44 | "scripts": { 45 | "dev": "iles dev --open", 46 | "build": "iles build", 47 | "preview": "iles preview --open" 48 | }, 49 | ``` 50 | 51 | - npm run dev: Starts the development server 52 | - npm run build: Creates a production build of the site 53 | - npm run preview: Preview the site after building 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/src/pages/guide/overview.mdx: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/src/pages/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | actionText: Get Started 5 | actionLink: /guide/introduction 6 | 7 | altActionText: Install 8 | altActionLink: /guide 9 | 10 | heroAlt: îles logo 11 | 12 | features: 13 | - title: 🏝 Partial Hydration 14 | details: Ship JS only for the interactive bits, by default that's zero. 15 | href: /guide/hydration 16 | - title: 🧱 Multi-Framework 17 | details: Build islands with Vue, Preact, SolidJS, Svelte, or plain JS. 18 | href: /guide/frameworks 19 | - title: 📖 Markdown Support 20 | details: Use components inside Markdown, with auto-import. 21 | href: /guide/markdown 22 | - title: ⚡ Batteries Included 23 | details: Layouts, routing, frontmatter for pages, plugins, and more. 24 | href: /guide/development 25 | --- 26 | -------------------------------------------------------------------------------- /docs/src/styles/all.css: -------------------------------------------------------------------------------- 1 | @import "~/styles/variables"; 2 | @import "~/styles/utilities"; 3 | @import "~/styles/global"; 4 | @import "~/styles/navbar"; 5 | @import "~/styles/themes"; 6 | @import "~/styles/highlight"; 7 | @import "~/styles/prose"; 8 | -------------------------------------------------------------------------------- /docs/src/styles/navbar.css: -------------------------------------------------------------------------------- 1 | #navbar-actions { 2 | height: 100%; 3 | width: 80px; 4 | } 5 | 6 | .navbar { 7 | /* https://unocss.dev/presets/wind#bracket-syntax-spaces */ 8 | @apply p-4 gap-4 grid-cols-[min-content_1fr_min-content] 9 | sm:(py-8 text-lg) 10 | lg:py-12; 11 | 12 | max-width: calc(6rem + var(--w-container) + (100vw - var(--w-container)) / 2); 13 | } 14 | 15 | .navbar-menu { 16 | @apply h-8 leading-5 place-items-center ml-auto; 17 | 18 | & > li + li, 19 | & + div, 20 | & + ile-root > div { 21 | @apply ml-4 lg:ml-8; 22 | } 23 | } 24 | 25 | .navbar-menu-item { 26 | @apply -mt-0.5; 27 | 28 | & > a { 29 | @apply opacity-80 pt-1 border-transparent border-bottom-2; 30 | 31 | transition: opacity 80ms ease, border-color 80ms ease; 32 | 33 | &:hover, 34 | &:active, 35 | &.active { 36 | @apply border-primary opacity-100; 37 | } 38 | } 39 | } 40 | 41 | .navbar-actions { 42 | @apply gap-2; 43 | } 44 | -------------------------------------------------------------------------------- /docs/src/styles/themes.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Colors */ 3 | --h-background: #fbfbfb; 4 | --h-foreground: #393a34; 5 | 6 | /* Tokens */ 7 | --h-boolean: #1c6b48; 8 | --h-builtin: #ab5959; 9 | --h-class: #795da3; 10 | --h-comment: #a0ada0; 11 | --h-constant: #2993a3; 12 | --h-decorator: #bd8f8f; 13 | --h-deleted: #a14f55; 14 | --h-function: #795da3; 15 | --h-keyword: #a71d5d; 16 | --h-literal: #2f8a89; 17 | --h-namespace: #b05a78; 18 | --h-number: #296aa3; 19 | --h-property: #b58451; 20 | --h-punctuation: #a71d5d; 21 | --h-regex: #ab5e3f; 22 | --h-string: #b56959; 23 | --h-operator: var(--h-punctuation); 24 | --h-variable: var(--h-literal); 25 | --h-symbol: var(--h-literal); 26 | --h-interpolation: var(--h-literal); 27 | --h-selector: var(--h-keyword); 28 | --h-keyword-control: var(--h-keyword); 29 | } 30 | 31 | html.dark { 32 | --h-foreground: #d4cfbf; 33 | --h-background: #1e1e1e; 34 | --h-boolean: #1c6b48; 35 | --h-builtin: #e0a569; 36 | --h-class: #54b1bf; 37 | --h-comment: #758575; 38 | --h-constant: var(--h-literal); 39 | --h-decorator: #bd8f8f; 40 | --h-deleted: #a14f55; 41 | --h-function: #67b1b5; 42 | --h-keyword: #e3428c; 43 | --h-literal: #429988; 44 | --h-namespace: #db889a; 45 | --h-number: #6394bf; 46 | --h-property: #dd8e6e; 47 | --h-punctuation: #858585; 48 | --h-regex: #ab5e3f; 49 | --h-string: #d48372; 50 | --h-variable: #c2b36e; 51 | } 52 | -------------------------------------------------------------------------------- /docs/src/styles/utilities.css: -------------------------------------------------------------------------------- 1 | .website-logo { 2 | @apply transition-transform duration-400 hover:scale-110; 3 | } 4 | 5 | .section-title { 6 | @apply text-3xl sm:text-4xl md:text-5xl leading-tight font-extrabold text-hard my-8; 7 | } 8 | 9 | .layout { 10 | @apply mx-auto px-8 my-8; 11 | 12 | max-width: calc(var(--w-container) + 15vw); 13 | } 14 | 15 | .container { 16 | margin: 0 auto; 17 | max-width: var(--w-container); 18 | } 19 | 20 | .hoverable { 21 | transition: background 80ms ease; 22 | 23 | &.active, 24 | &:hover { 25 | background: var(--bg-highlight); 26 | } 27 | } 28 | 29 | .transition-bg { 30 | transition: background 0.3s ease; 31 | } 32 | -------------------------------------------------------------------------------- /docs/src/styles/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* https://github.com/unocss/unocss/blob/main/packages/reset/tailwind.md */ 3 | --un-default-border-color: var(--br-normal); 4 | --navbar-height: 3.5rem; 5 | --footer-height: 172px; 6 | --full-viewport: calc(100vh - var(--navbar-height)); 7 | --br-normal: rgba(126, 135, 160, 0.15); 8 | --bg-html: theme('colors.white'); 9 | --bg-scrollbar-track: var(--bg-highlight); 10 | --bg-scrollbar-thumb: var(--bg-soft); 11 | --bg-scrollbar-thumb-hover: var(--bg-primary); 12 | --bg-highlight: rgba(126, 185, 220, 0.15); 13 | --bg-primary: var(--fc-primary-soft); 14 | --bg-soft: rgba(126, 185, 220, 0.35); 15 | --bg-subtle: theme('colors.gray.50'); 16 | --fc-softer: theme('colors.gray.400'); 17 | --fc-soft: theme('colors.gray.500'); 18 | --fc: theme('colors.gray.700'); 19 | --fc-hard: theme('colors.gray.800'); 20 | --fc-intense: theme('colors.gray.900'); 21 | --fc-primary-soft: theme('colors.lightblue.500'); 22 | --fc-primary: theme('colors.lightblue.600'); 23 | --fc-primary-intense: theme('colors.lightblue.700'); 24 | --w-container: 1376px; 25 | } 26 | 27 | @screen md { 28 | :root { 29 | --navbar-height: 3.75rem; 30 | } 31 | } 32 | 33 | html.dark { 34 | --bg-html: theme('colors.warmgray.900'); 35 | --br-normal: rgba(226, 235, 255, 0.15); 36 | --bg-soft: rgba(195, 185, 185, 0.15); 37 | --fc-softer: theme('colors.warmgray.500'); 38 | --fc-soft: theme('colors.warmgray.500'); 39 | --fc: theme('colors.warmgray.300'); 40 | --fc-hard: theme('colors.warmgray.200'); 41 | --fc-intense: theme('colors.warmgray.100'); 42 | --fc-primary-soft: theme('colors.lightblue.500'); 43 | --fc-primary-intense: theme('colors.lightblue.500'); 44 | 45 | color-scheme: dark 46 | } 47 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "moduleResolution": "node", 7 | "allowJs": true, 8 | "jsx": "preserve", 9 | "jsxFactory": "h", 10 | "jsxFragmentFactory": "Fragment", 11 | "strict": true, 12 | "declaration": true, 13 | "noUnusedLocals": true, 14 | "skipLibCheck": true, 15 | "esModuleInterop": true, 16 | "lib": ["esnext", "DOM"], 17 | "types": [ 18 | "@vue-macros/reactivity-transform/macros-global", 19 | "vite-plugin-pwa/client" 20 | ], 21 | "paths": { 22 | "~/*": ["src/*"] 23 | } 24 | }, 25 | "exclude": [ 26 | "node_modules/cypress" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /docs/uno.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, 3 | presetIcons, 4 | // presetTypography, 5 | presetUno, 6 | transformerDirectives, 7 | } from 'unocss' 8 | import transformerVariantGroup from '@unocss/transformer-variant-group' 9 | 10 | export default defineConfig({ 11 | transformers: [transformerDirectives(), transformerVariantGroup()], 12 | presets: [ 13 | presetUno(), 14 | // presetTypography(), 15 | presetIcons({ 16 | prefix: 'i-', // default prefix 17 | }), 18 | 19 | ], 20 | safelist: ['blockquote', 'figure', 'code'], 21 | blocklist: ['content-type', 'content', 'container', 'table', '", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/ElMassimo/iles" 28 | }, 29 | "homepage": "https://github.com/ElMassimo/iles", 30 | "bugs": "https://github.com/ElMassimo/iles/issues", 31 | "dependencies": { 32 | "estree-walker": "^3.0", 33 | "hast-util-to-string": "^3.0.0" 34 | }, 35 | "devDependencies": { 36 | "@types/estree": "^1.0.6", 37 | "@types/estree-jsx": "^1.0.5", 38 | "@types/hast": "^3.0.4", 39 | "iles": "workspace:*", 40 | "tsup": "8.2.4", 41 | "typescript": "^5.6.3", 42 | "unified": "^11.0.5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/excerpt/src/excerpt.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (...args) => new Promise((resolve, reject) => { 2 | import('../dist/excerpt.js') 3 | .then(m => resolve(m.default(...args))) 4 | .catch(reject) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/excerpt/src/excerpt.ts: -------------------------------------------------------------------------------- 1 | import type { IlesModule } from 'iles' 2 | import type { ExcerptOptions, Options, SeparatorFn } from './types' 3 | import type { Element, Comment } from 'hast' 4 | import { recmaPlugin } from './recma-plugin' 5 | import { rehypePlugin } from './rehype-plugin' 6 | 7 | declare module 'iles' { 8 | interface PageMeta { 9 | /** 10 | * Excerpt for MDX documents. 11 | */ 12 | excerpt?: string 13 | } 14 | } 15 | 16 | export * from './types' 17 | 18 | /** 19 | * An iles module that sets `meta.excerpt` for MDX documents. 20 | * Also enables an `excerpt: true` prop in MDX components to render HTML. 21 | */ 22 | export default function IlesExcerpts (userOptions: ExcerptOptions = {}): IlesModule { 23 | const { separator = ['excerpt', 'Excerpt'], ...rest } = userOptions 24 | const options: Options = { ...rest, isSeparator: separatorFnFrom(separator) } 25 | 26 | return { 27 | name: '@islands/excerpt', 28 | markdown: { 29 | rehypePlugins: [ 30 | [rehypePlugin, options], 31 | ], 32 | recmaPlugins: [ 33 | recmaPlugin, 34 | ], 35 | }, 36 | } 37 | } 38 | 39 | function separatorFnFrom (separator: string | string[] | SeparatorFn): SeparatorFn { 40 | if (isSeparatorFn(separator)) 41 | return separator 42 | 43 | const separators = new Set(Array.isArray(separator) ? separator : [separator]) 44 | 45 | return (node) => { 46 | if (node.type === 'element') return separators.has((node as Element).tagName) 47 | // @ts-ignore 48 | if (node.type === 'mdxJsxFlowElement') return separators.has(node.name) 49 | if (node.type === 'comment') return separators.has((node as Comment).value.trim()) 50 | return false 51 | } 52 | } 53 | 54 | function isSeparatorFn (val: any): val is SeparatorFn { 55 | return typeof val === 'function' 56 | } 57 | -------------------------------------------------------------------------------- /packages/excerpt/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { Node } from 'hast' 2 | import type { VFile } from 'vfile' 3 | 4 | export type ExtractFn = (content: string, vfile: VFile) => string | undefined 5 | 6 | export type SeparatorFn = (node: Node, index: number) => boolean 7 | 8 | export interface ExcerptOptions { 9 | /** 10 | * Function to extract the excerpt from an MDX document. 11 | */ 12 | extract?: ExtractFn 13 | /** 14 | * Tag name(s) of the separator or a function that returns true when it finds one. 15 | * @default ['excerpt', 'Excerpt'] 16 | */ 17 | separator?: string | string[] | SeparatorFn 18 | /** 19 | * An optional max length for the extracted excerpt. 20 | */ 21 | maxLength?: number 22 | } 23 | 24 | export interface Options { 25 | extract?: ExtractFn 26 | isSeparator: SeparatorFn 27 | maxLength?: number 28 | } 29 | -------------------------------------------------------------------------------- /packages/excerpt/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | splitting: true, 7 | format: ['esm'], 8 | } 9 | -------------------------------------------------------------------------------- /packages/feed/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

@islands/feed

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | [routing]: https://iles-docs.netlify.app/guide/routing 23 | [feed]: https://github.com/jpmonette/feed 24 | [rss]: https://iles-docs.netlify.app/guide/rss 25 | 26 | An [îles] module to generate feeds for your site: 27 | 28 | - 📻 supports RSS, Atom, and JSON feeds 29 | 30 | - ⚡️ HMR during development to debug the result 31 | 32 | - 💪🏼 strongly typed, powered by [`feed`][feed] 33 | 34 | ### Installation 💿 35 | 36 | ```ts 37 | // iles.config.ts 38 | import { defineConfig } from 'iles' 39 | 40 | export default defineConfig({ 41 | modules: [ 42 | '@islands/feed', 43 | ], 44 | }) 45 | ``` 46 | 47 | See the [_RSS Feeds_ section of the docs][rss] for usage instructions. 48 | -------------------------------------------------------------------------------- /packages/feed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/feed", 3 | "version": "0.10.0-beta.1", 4 | "scripts": { 5 | "dev": "npm run build -- --watch", 6 | "build": "tsup src/types.ts src/feed.ts src/render-feed.ts", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix" 9 | }, 10 | "files": [ 11 | "dist", 12 | "src" 13 | ], 14 | "types": "dist/feed.d.ts", 15 | "type": "module", 16 | "exports": { 17 | ".": { 18 | "import": "./dist/feed.js", 19 | "require": "./src/feed.cjs" 20 | }, 21 | "./package.json": "./package.json" 22 | }, 23 | "funding": "https://github.com/sponsors/ElMassimo", 24 | "author": "Máximo Mussini ", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/ElMassimo/iles" 28 | }, 29 | "homepage": "https://github.com/ElMassimo/iles", 30 | "bugs": "https://github.com/ElMassimo/iles/issues", 31 | "devDependencies": { 32 | "iles": "workspace:*", 33 | "vue": "^3.5.12" 34 | }, 35 | "peerDependencies": { 36 | "iles": "workspace:*", 37 | "vue": "^3.3.4" 38 | }, 39 | "dependencies": { 40 | "feed": "^4.2", 41 | "pathe": "^1.1.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/feed/src/feed.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (...args) => new Promise((resolve, reject) => { 2 | import('../dist/feed.js') 3 | .then(m => resolve(m.default(...args))) 4 | .catch(reject) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/feed/src/feed.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url' 2 | import { dirname, join } from 'pathe' 3 | import type { IlesModule } from 'iles' 4 | 5 | export * from './types' 6 | 7 | const __dirname = dirname(fileURLToPath(import.meta.url)) 8 | 9 | /** 10 | * An iles module that provides a component to generate RSS, Atom, and JSON feeds. 11 | */ 12 | export default function IlesFeed (): IlesModule { 13 | return { 14 | name: '@islands/feed', 15 | components: { 16 | resolvers: [ 17 | (name) => { 18 | if (name === 'RenderFeed') 19 | return { name: 'RenderFeed', from: join(__dirname, 'render-feed') } 20 | }, 21 | ], 22 | }, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/feed/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { FeedOptions, Item as ResolvedItem, Author, Extension } from 'feed' 2 | import type { VueRenderable } from 'iles' 3 | 4 | export type AsyncContent = string | VueRenderable 5 | 6 | export type { FeedOptions, ResolvedItem, Author, Extension } 7 | 8 | export interface FeedItem extends Omit { 9 | description?: AsyncContent 10 | content?: AsyncContent 11 | } 12 | 13 | export type FeedFormat = 'atom1' | 'rss2' | 'json1' 14 | -------------------------------------------------------------------------------- /packages/feed/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | splitting: false, 7 | format: ['esm'], 8 | } 9 | -------------------------------------------------------------------------------- /packages/headings/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

@islands/headings

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | [docs]: https://iles-docs.netlify.app 23 | [rehype]: https://github.com/rehypejs/rehype 24 | [markdown]: https://iles-docs.netlify.app/guide/markdown 25 | 26 | An [îles] module that injects a [rehype] plugin to parse headings in 27 | [MDX documents][markdown]: 28 | 29 | - 🔗 adds an id to headings and injects an anchor tag to link them 30 | 31 | - 🏷 automatically extracts the title from an `

` and sets `frontmatter.title` 32 | 33 | - 📖 sets `meta.headings` to enable rendering sidebars and table of contents 34 | 35 | ### Usage 🚀 36 | 37 | ```ts 38 | // iles.config.ts 39 | import { defineConfig } from 'iles' 40 | 41 | export default defineConfig({ 42 | modules: [ 43 | '@islands/headings', 44 | ], 45 | }) 46 | ``` 47 | -------------------------------------------------------------------------------- /packages/headings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/headings", 3 | "version": "0.10.0-beta.1", 4 | "scripts": { 5 | "dev": "npm run build -- --watch", 6 | "build": "tsup src/headings.ts", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix" 9 | }, 10 | "type": "module", 11 | "files": [ 12 | "dist", 13 | "src" 14 | ], 15 | "types": "dist/headings.d.ts", 16 | "exports": { 17 | ".": { 18 | "import": "./dist/headings.js", 19 | "require": "./src/headings.cjs" 20 | }, 21 | "./package.json": "./package.json" 22 | }, 23 | "funding": "https://github.com/sponsors/ElMassimo", 24 | "author": "Máximo Mussini ", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/ElMassimo/iles" 28 | }, 29 | "homepage": "https://github.com/ElMassimo/iles", 30 | "bugs": "https://github.com/ElMassimo/iles/issues", 31 | "dependencies": { 32 | "hast-util-heading-rank": "^3.0.0", 33 | "hast-util-to-string": "^3.0.0" 34 | }, 35 | "devDependencies": { 36 | "@types/estree": "^1.0.5", 37 | "iles": "workspace:*", 38 | "slugo": "^0.4.0", 39 | "tsup": "8.2.4", 40 | "typescript": "^5.6.3", 41 | "unified": "^11.0.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/headings/src/headings.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (...args) => new Promise((resolve, reject) => { 2 | import('../dist/headings.js') 3 | .then(m => resolve(m.default(...args))) 4 | .catch(reject) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/headings/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | splitting: true, 7 | format: ['esm'], 8 | } 9 | -------------------------------------------------------------------------------- /packages/hydration/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

@islands/hydration

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | 23 | Internal utility for [îles] to hydrate Vue, Svelte, Preact, and Solid components. 24 | -------------------------------------------------------------------------------- /packages/hydration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/hydration", 3 | "description": "Hydration utilities for îles", 4 | "version": "0.10.0-beta.1", 5 | "scripts": { 6 | "dev": "npm run build -- --watch", 7 | "build": "tsup hydration.ts preact.ts vue.ts vanilla.ts solid.ts svelte.ts", 8 | "lint": "eslint .", 9 | "lint:fix": "eslint . --fix" 10 | }, 11 | "type": "module", 12 | "files": [ 13 | "dist", 14 | "island.svelte" 15 | ], 16 | "types": "dist/hydration.d.ts", 17 | "module": "dist/hydration.js", 18 | "exports": { 19 | ".": "./dist/hydration.js", 20 | "./preact": "./dist/preact.js", 21 | "./solid": "./dist/solid.js", 22 | "./svelte": "./dist/svelte.js", 23 | "./vanilla": "./dist/vanilla.js", 24 | "./vue": "./dist/vue.js", 25 | "./dist/*": "./dist/*", 26 | "./package.json": "./package.json" 27 | }, 28 | "keywords": [ 29 | "vite", 30 | "vue", 31 | "islands", 32 | "ssg" 33 | ], 34 | "author": "Máximo Mussini", 35 | "license": "MIT", 36 | "homepage": "https://github.com/ElMassimo/iles", 37 | "bugs": { 38 | "url": "https://github.com/ElMassimo/iles/issues" 39 | }, 40 | "devDependencies": { 41 | "preact": "^10.24.3", 42 | "preact-render-to-string": "^6.5.11", 43 | "solid-js": "^1.9.3", 44 | "svelte": "^5.1.13", 45 | "tsup": "8.2.4", 46 | "typescript": "^5.6.3", 47 | "vite": "^5.4.10", 48 | "vue": "^3.5.12" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/hydration/preact.ts: -------------------------------------------------------------------------------- 1 | import { h, render, toChildArray } from 'preact' 2 | import type { FunctionComponent as Component } from 'preact' 3 | import type { Props, Slots } from './types' 4 | import { onDispose } from './hydration' 5 | 6 | export default function createIsland (component: Component, id: string, el: Element, props: Props, slots: Slots | undefined) { 7 | render(createElement(component, props, slots), el) 8 | 9 | if (import.meta.env.DISPOSE_ISLANDS) 10 | onDispose(id, () => render(null, el)) 11 | 12 | if (import.meta.env.DEV) 13 | (window as any).__ILE_DEVTOOLS__?.onHydration({ id, el, props, slots, component, framework: 'preact' }) 14 | } 15 | 16 | /** 17 | * Preact doesn't have an equivalent for createStaticVNode. 18 | */ 19 | const IslandContent = (props: any) => { 20 | return h('iles-content', { dangerouslySetInnerHTML: { __html: props.content } }) 21 | } 22 | IslandContent.shouldComponentUpdate = () => false 23 | 24 | export function createElement (component: Component, props: Props, slots: Slots | undefined) { 25 | const content = slots?.default 26 | const children = content ? toChildArray(h(IslandContent, { content })) : null 27 | return h(component, props, children) 28 | } 29 | -------------------------------------------------------------------------------- /packages/hydration/solid.ts: -------------------------------------------------------------------------------- 1 | import { hydrate, render, createComponent } from 'solid-js/web' 2 | import type { Component } from 'solid-js' 3 | import type { Props, Slots } from './types' 4 | import { onDispose } from './hydration' 5 | 6 | export default function createIsland (component: Component, id: string, el: Element, props: Props, slots: Slots | undefined) { 7 | if (import.meta.env.DEV) 8 | // @ts-ignore 9 | window._$HY ||= { events: [], completed: new WeakSet(), r: {} } 10 | 11 | const dispose = (import.meta.env.DEV ? render : hydrate)(() => createComponent(component, { ...props, children: createContent(slots) }), el, { renderId: id }) 12 | 13 | if (import.meta.env.DISPOSE_ISLANDS) 14 | onDispose(id, dispose) 15 | 16 | if (import.meta.env.DEV) 17 | (window as any).__ILE_DEVTOOLS__?.onHydration({ id, el, props, slots, component, framework: 'solid' }) 18 | } 19 | 20 | function createContent (slots: Slots | undefined) { 21 | if (!slots?.default) return 22 | const content = document.createElement('iles-content') 23 | content.innerHTML = slots.default 24 | return Array.from(content.childNodes) 25 | } 26 | -------------------------------------------------------------------------------- /packages/hydration/svelte.ts: -------------------------------------------------------------------------------- 1 | import { createRawSnippet, mount, unmount, type Snippet } from 'svelte' 2 | import type { Props, Slots } from './types' 3 | import { onDispose } from './hydration' 4 | 5 | type Component = any 6 | 7 | export default function createIsland (Component: Component, id: string, el: Element, props: Props, slots: Slots | undefined = {}) { 8 | let children 9 | let $$slots: Record & { default?: boolean } | undefined 10 | let renderFns: Record = {} 11 | 12 | Object.entries(slots).forEach(([slotName, html]) => { 13 | const fnName = slotName === 'default' ? 'children' : slotName 14 | renderFns[fnName] = createRawSnippet(() => ({ render: () => html })) 15 | 16 | $$slots ??= {} 17 | if (slotName === 'default') { 18 | $$slots.default = true 19 | children = renderFns[fnName] 20 | } 21 | else { 22 | $$slots[fnName] = renderFns[fnName] 23 | } 24 | }) 25 | 26 | 27 | const component = mount(Component, { 28 | target: el, 29 | props: { 30 | ...props, 31 | children, 32 | $$slots, 33 | ...renderFns, 34 | }, 35 | }); 36 | 37 | if (import.meta.env.DISPOSE_ISLANDS) 38 | onDispose(id, () => unmount(component)) 39 | 40 | if (import.meta.env.DEV) 41 | (window as any).__ILE_DEVTOOLS__?.onHydration({ id, el, props, slots, component, framework: 'svelte' }) 42 | } 43 | -------------------------------------------------------------------------------- /packages/hydration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": ["esnext", "DOM"], 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "strictNullChecks": true, 9 | "moduleResolution": "Node", 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/hydration/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'esnext', 6 | splitting: true, 7 | format: ['esm'], 8 | external: [ 9 | 'vue', 10 | 'preact', 11 | 'preact-render-to-string', 12 | 'solid-js', 13 | 'solid-js/web', 14 | 'solid-js/web/dist/web.js', 15 | 'solid-js/web/dist/server.js', 16 | 'svelte', 17 | 'svelte/server', 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /packages/hydration/types.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import type Vue from './vue' 4 | 5 | export type Framework = 'vue' | 'preact' | 'solid' | 'svelte' | 'vanilla' 6 | export type FrameworkFn = typeof Vue 7 | export type AsyncFrameworkFn = () => Promise 8 | export type Component = any 9 | export type AsyncComponent = () => Component 10 | export type Id = string 11 | export type Props = Record 12 | export type Slots = Record 13 | -------------------------------------------------------------------------------- /packages/hydration/vanilla.ts: -------------------------------------------------------------------------------- 1 | import type { Component, Props, Slots } from './types' 2 | import { onDispose } from './hydration' 3 | 4 | type MaybeAsync = T | Promise 5 | export type OnDisposeFn = () => void 6 | export type OnLoadFn = (el: Element, props: Props, slots: Slots | undefined) => MaybeAsync 7 | 8 | const isFunction = (val: any): val is Function => typeof val === 'function' 9 | 10 | // Internal: Calls the function to run custom client code. 11 | export default async function createIsland (component: Component | OnLoadFn, id: string, el: Element, props: Props, slots: Slots | undefined) { 12 | if (isFunction(component)) { 13 | const dispose = await component(el, props, slots) 14 | 15 | if (import.meta.env.DISPOSE_ISLANDS && isFunction(dispose)) 16 | onDispose(id, dispose) 17 | } 18 | 19 | if (import.meta.env.DEV) 20 | (window as any).__ILE_DEVTOOLS__?.onHydration({ id, el, props, slots, component, framework: 'none' }) 21 | } 22 | -------------------------------------------------------------------------------- /packages/hydration/vue.ts: -------------------------------------------------------------------------------- 1 | import { h, createApp as createClientApp, createStaticVNode, createSSRApp } from 'vue' 2 | import type { DefineComponent as Component, Component as App } from 'vue' 3 | import type { Props, Slots } from './types' 4 | import { onDispose } from './hydration' 5 | 6 | const createVueApp = import.meta.env.SSR ? createSSRApp : createClientApp 7 | 8 | // Internal: Creates a Vue app and mounts it on the specified island root. 9 | export default function createVueIsland (component: Component, id: string, el: Element, props: Props, slots: Slots | undefined) { 10 | const slotFns = slots && Object.fromEntries(Object.entries(slots).map(([slotName, content]) => { 11 | return [slotName, () => (createStaticVNode as any)(content)] 12 | })) 13 | 14 | const appDefinition: App = { render: () => h(component, props, slotFns) } 15 | 16 | if (import.meta.env.DEV) 17 | appDefinition.name = `Island: ${nameFromFile(component.__file)}` 18 | 19 | const app = createVueApp(appDefinition) 20 | app.mount(el!, Boolean(slots)) 21 | 22 | if (import.meta.env.DISPOSE_ISLANDS) 23 | onDispose(id, app.unmount) 24 | 25 | if (import.meta.env.DEV) 26 | (window as any).__ILE_DEVTOOLS__?.onHydration({ id, el, props, slots, component }) 27 | } 28 | 29 | function nameFromFile (file?: string) { 30 | const regex = /(\w+?)(?:\.vue)?$/ 31 | return file?.match(regex)?.[1] || file 32 | } 33 | -------------------------------------------------------------------------------- /packages/icons/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.10.0-beta.1](https://github.com/ElMassimo/iles/compare/icons@0.8.0...icons@0.10.0-beta.1) (2024-12-04) 2 | 3 | 4 | ### Features 5 | 6 | * update dependencies (latest vite) ([#281](https://github.com/ElMassimo/iles/issues/281)) ([c291852](https://github.com/ElMassimo/iles/commit/c29185255e41e63830236ceb4c67de599aae2012)) 7 | 8 | 9 | 10 | # [0.8.0](https://github.com/ElMassimo/iles/compare/icons@0.1.1...icons@0.8.0) (2022-07-14) 11 | 12 | 13 | 14 | ## [0.1.1](https://github.com/ElMassimo/iles/compare/icons@0.1.0...icons@0.1.1) (2021-11-03) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * allow manually requiring from CJS applications ([664235d](https://github.com/ElMassimo/iles/commit/664235dc0414fa7c9bb37e9c92bddaca5d01bd6e)) 20 | 21 | 22 | 23 | # 0.1.0 (2021-11-02) 24 | 25 | - Initial Version 26 | 27 | 28 | -------------------------------------------------------------------------------- /packages/icons/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

@islands/icons

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | [components]: https://iles-docs.netlify.app/guide/development 23 | [unplugin-icons]: https://github.com/antfu/unplugin-icons 24 | 25 | An [îles] module to add and configure [unplugin-icons]: 26 | 27 | - ✨ `autoInstall` enabled by default, and `icon` prefix to prevent conflicts 28 | 29 | - 🧱 configures the `unplugin-vue-components` resolver automatically 30 | 31 | - 🎨 files in the `/icons` dir available as the `app` collection, `", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/ElMassimo/iles" 28 | }, 29 | "homepage": "https://github.com/ElMassimo/iles", 30 | "bugs": "https://github.com/ElMassimo/iles/issues", 31 | "dependencies": { 32 | "unplugin-icons": "^0.19.0" 33 | }, 34 | "devDependencies": { 35 | "iles": "workspace:*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/icons/src/icons.mjs: -------------------------------------------------------------------------------- 1 | import mod from '../dist/icons.cjs' 2 | 3 | export default mod.default 4 | -------------------------------------------------------------------------------- /packages/icons/src/icons.ts: -------------------------------------------------------------------------------- 1 | import type { IlesModule } from 'iles' 2 | import type { Options } from 'unplugin-icons' 3 | 4 | import icons from 'unplugin-icons/vite' 5 | import iconsResolver from 'unplugin-icons/resolver' 6 | import { FileSystemIconLoader } from 'unplugin-icons/loaders' 7 | 8 | interface ModuleOptions extends Options { 9 | resolver?: Parameters[0] 10 | } 11 | 12 | /** 13 | * An iles module that configures unplugin-icons to autoInstall collections and 14 | * be able to use local icons in the /icons or /images directories. 15 | * 16 | * @param options - Optional options to configure the output. 17 | */ 18 | export default function IlesIcons (options?: ModuleOptions): IlesModule { 19 | const { resolver, ...iconsOptions } = options || {} 20 | 21 | return { 22 | name: '@islands/icons', 23 | components: { 24 | resolvers: [ 25 | iconsResolver({ 26 | prefix: 'icon', 27 | customCollections: ['app'], 28 | ...resolver, 29 | }), 30 | ], 31 | }, 32 | vite: { 33 | plugins: [ 34 | icons({ 35 | autoInstall: true, 36 | customCollections: { 37 | app: FileSystemIconLoader('./icons'), 38 | }, 39 | ...iconsOptions, 40 | }), 41 | ], 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/icons/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | splitting: true, 7 | format: ['cjs'], 8 | } 9 | -------------------------------------------------------------------------------- /packages/iles/.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | src/client/shared.ts 3 | src/node/shared.ts 4 | *.log 5 | .DS_Store 6 | .vite_opt_cache 7 | dist 8 | node_modules 9 | TODOs.md 10 | .vscode 11 | /.iles-ssg-temp 12 | -------------------------------------------------------------------------------- /packages/iles/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /packages/iles/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

iles

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | [cli]: https://iles-docs.netlify.app/guide#usage 23 | [client library]: https://iles-docs.netlify.app/guide/development#using-page-data 24 | 25 | The main [îles] package, which provides a [cli] and the [client library]. 26 | -------------------------------------------------------------------------------- /packages/iles/bin/iles.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import '../dist/node/cli.js' 4 | -------------------------------------------------------------------------------- /packages/iles/config.js: -------------------------------------------------------------------------------- 1 | // NOTE: This helper file allows to provide it as a config path to Vite or 2 | // Vitest without using the iles executable. 3 | export default async (env, root = process.cwd()) => { 4 | const { default: IslandsPlugins, resolveConfig, mergeConfig } = await import('./dist/node/index.js') 5 | 6 | const config = await resolveConfig(root) 7 | 8 | return mergeConfig(config.vite, { 9 | plugins: IslandsPlugins(config), 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /packages/iles/index.cjs: -------------------------------------------------------------------------------- 1 | // type utils 2 | module.exports.defineConfig = (config) => config 3 | 4 | // proxy cjs utils (sync functions) 5 | Object.assign(module.exports, require('./dist/node-cjs/publicUtils.cjs')) 6 | 7 | // async functions, can be redirect from ESM build 8 | const asyncFunctions = [ 9 | 'build', 10 | 'resolveConfig', 11 | ] 12 | asyncFunctions.forEach((name) => { 13 | module.exports[name] = (...args) => 14 | import('./dist/node/index.js').then((i) => i[name](...args)) 15 | }) 16 | 17 | // some sync functions are marked not supported due to their complexity and uncommon usage 18 | const unsupportedCJS = ['default'] 19 | unsupportedCJS.forEach((name) => { 20 | module.exports[name] = () => { 21 | throw new Error( 22 | `"${name}" is not supported in CJS build of îles.\nPlease use ESM or dynamic imports \`const { ${name} } = await import('iles')\`.` 23 | ) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /packages/iles/jsx-dev-runtime.js: -------------------------------------------------------------------------------- 1 | export * from './jsx-runtime' 2 | export { jsx as jsxDEV } from './jsx-runtime' 3 | -------------------------------------------------------------------------------- /packages/iles/jsx-runtime.js: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent as defineVueComponent, 3 | resolveComponent, 4 | createVNode, 5 | createStaticVNode as raw, 6 | Fragment, 7 | } from 'vue' 8 | 9 | // Internal: Compatibility layer with the automatic JSX runtime of React. 10 | // 11 | // NOTE: Supports v-slots for consistency with @vue/babel-plugin-jsx. 12 | function jsx (type, { children, 'v-slots': vSlots, ...props }) { 13 | let slots 14 | 15 | if (children) { 16 | // Normalize the default slot into a function returning an array of vnodes. 17 | if (!Array.isArray(children)) children = [children] 18 | 19 | slots = type === Fragment 20 | ? children 21 | : { ...vSlots, default: () => children } 22 | } 23 | else { 24 | // Allow empty fragment expressions. 25 | if (type === Fragment) 26 | return null 27 | 28 | slots = vSlots || null 29 | } 30 | 31 | return createVNode(type, props, slots) 32 | } 33 | 34 | // Internal: Extends it to be a stateful component that can perform prop checks. 35 | function defineComponent (MDXContent, definition) { 36 | return defineVueComponent({ 37 | ...definition, 38 | props: { 39 | components: { type: Object }, 40 | excerpt: { type: Boolean }, 41 | }, 42 | render (props) { 43 | if (!props) props = this ? { ...this.$props, ...this.$attrs } : {} 44 | return MDXContent(props) 45 | }, 46 | }) 47 | } 48 | 49 | export { 50 | Fragment, 51 | jsx, 52 | jsx as jsxs, 53 | defineComponent, 54 | resolveComponent, 55 | raw, 56 | } 57 | -------------------------------------------------------------------------------- /packages/iles/scripts/copyClient.js: -------------------------------------------------------------------------------- 1 | import { copy } from 'fs-extra' 2 | import { globSync } from 'tinyglobby' 3 | 4 | function toDest(file) { 5 | return file.replace(/src\//, 'dist/') 6 | } 7 | 8 | globSync(['src/client/**']).forEach((file) => { 9 | if (/(\.ts|tsconfig\.json)$/.test(file)) return 10 | copy(file, toDest(file)) 11 | }) 12 | -------------------------------------------------------------------------------- /packages/iles/scripts/copyShared.js: -------------------------------------------------------------------------------- 1 | import { copy } from 'fs-extra' 2 | import { globSync } from 'tinyglobby' 3 | 4 | globSync(['src/shared/**/*.ts']).forEach(async (file) => { 5 | await Promise.all([ 6 | copy(file, file.replace(/^src\/shared\//, 'src/node/')), 7 | copy(file, file.replace(/^src\/shared\//, 'src/client/')) 8 | ]) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/iles/scripts/watchAndCopy.js: -------------------------------------------------------------------------------- 1 | import { copy, remove } from 'fs-extra' 2 | import { watch } from 'chokidar' 3 | import { normalizePath } from 'vite' 4 | 5 | function toClientAndNode (method, file) { 6 | file = normalizePath(file) 7 | if (method === 'copy') { 8 | copy(file, file.replace(/^src\/shared\//, 'src/node/')) 9 | copy(file, file.replace(/^src\/shared\//, 'src/client/')) 10 | } 11 | else if (method === 'remove') { 12 | remove(file.replace(/^src\/shared\//, 'src/node/')) 13 | remove(file.replace(/^src\/shared\//, 'src/client/')) 14 | } 15 | } 16 | 17 | function toDist (file) { 18 | return normalizePath(file).replace(/^src\//, 'dist/') 19 | } 20 | 21 | // copy shared files to the client and node directory whenever they change. 22 | watch('src/shared/**/*.ts') 23 | .on('change', file => toClientAndNode('copy', file)) 24 | .on('add', file => toClientAndNode('copy', file)) 25 | .on('unlink', file => toClientAndNode('remove', file)) 26 | 27 | // copy non ts files, such as an html or css, to the dist directory whenever 28 | // they change. 29 | watch('src/client/**/!(*.ts|tsconfig.json)') 30 | .on('change', file => copy(file, toDist(file))) 31 | .on('add', file => copy(file, toDist(file))) 32 | .on('unlink', file => remove(toDist(file))) 33 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/components/App.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 58 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/components/NotFound.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 34 | 35 | 64 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/composables/appConfig.ts: -------------------------------------------------------------------------------- 1 | import { inject } from 'vue' 2 | 3 | import type { App, InjectionKey } from 'vue' 4 | import type { AppClientConfig } from '../../shared' 5 | 6 | export const appConfigSymbol: InjectionKey = Symbol('[iles-app-config]') 7 | 8 | export function installAppConfig (app: App, config: AppClientConfig) { 9 | app.provide(appConfigSymbol, config) 10 | } 11 | 12 | export function useAppConfig (): AppClientConfig { 13 | const data = inject(appConfigSymbol) 14 | if (!data) throw new Error('App config not properly injected in app') 15 | return data 16 | } 17 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/composables/islandDefinitions.ts: -------------------------------------------------------------------------------- 1 | import { useSSRContext } from 'vue' 2 | import { useRoute } from 'vue-router' 3 | 4 | import type { IslandDefinition } from '../../shared' 5 | 6 | export function useIslandsForPath (): IslandDefinition[] { 7 | const context = useSSRContext() 8 | if (!context) throw new Error('SSR context not found when rendering islands.') 9 | if (!context.islandsByPath) throw new Error('SSR context is missing islands.') 10 | 11 | const currentRoute = useRoute() 12 | 13 | return (context.islandsByPath[currentRoute.path] ||= []) 14 | } 15 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/composables/mdxComponents.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from 'vue' 2 | import { inject, provide } from 'vue' 3 | 4 | import type { AppContext, MDXComponents, UserApp } from '../../shared' 5 | import { useAppConfig } from './appConfig' 6 | 7 | export const mdxComponentsKey: InjectionKey = Symbol('[iles-mdx-components]') 8 | 9 | // Public: Allows to globally obtain replacements for built-ins, such as img. 10 | export function useMDXComponents () { 11 | return inject(mdxComponentsKey) 12 | } 13 | 14 | // Public: Allows to globally provide replacements for built-ins, such as img. 15 | export function provideMDXComponents (mdxComponents: MDXComponents) { 16 | const overrideElements = useAppConfig()?.overrideElements 17 | if (overrideElements) { 18 | for (const key in mdxComponents) { 19 | if (key === key.toLowerCase() && !overrideElements.includes(key)) 20 | console.warn(`Provided an MDX shortcode for '${key}', but it's being optimized. You must specify '${key}' in mdxComponents in app.ts`) 21 | } 22 | } 23 | provide(mdxComponentsKey, mdxComponents) 24 | } 25 | 26 | export async function installMDXComponents (context: AppContext, { mdxComponents }: UserApp) { 27 | const components = mdxComponents 28 | ? typeof mdxComponents === 'function' ? await mdxComponents(context) : mdxComponents 29 | : {} 30 | context.app.provide(mdxComponentsKey, components) 31 | } 32 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/composables/reactivity.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | import type { Ref } from 'vue' 4 | 5 | /** 6 | * Converts ref to a reactive value. 7 | * 8 | * @see https://vueuse.org/toReactive 9 | */ 10 | export function toReactive (objectRef: Ref): T { 11 | const proxy = new Proxy({}, { 12 | get (_, p, receiver) { 13 | return Reflect.get(objectRef.value, p, receiver) 14 | }, 15 | set (_, p, value) { 16 | (objectRef.value as any)[p] = value 17 | return true 18 | }, 19 | deleteProperty (_, p) { 20 | return Reflect.deleteProperty(objectRef.value, p) 21 | }, 22 | has (_, p) { 23 | return Reflect.has(objectRef.value, p) 24 | }, 25 | ownKeys () { 26 | return Object.keys(objectRef.value) 27 | }, 28 | getOwnPropertyDescriptor () { 29 | return { 30 | enumerable: true, 31 | configurable: true, 32 | } 33 | }, 34 | }) 35 | 36 | return reactive(proxy) as T 37 | } 38 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/composables/renderer.ts: -------------------------------------------------------------------------------- 1 | import { useSSRContext } from 'vue' 2 | import type { Framework, PrerenderFn } from '@islands/prerender' 3 | 4 | export function useRenderer (framework: Framework): PrerenderFn | undefined { 5 | const context = useSSRContext() 6 | if (!context) throw new Error('SSR context not found when rendering islands.') 7 | if (!context.renderers) throw new Error('Island renderers are missing in SSR context.') 8 | return context.renderers[framework] 9 | } 10 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/composables/routerLinks.ts: -------------------------------------------------------------------------------- 1 | // Use vue-router navigation during development even when using anchor tags. 2 | 3 | import { useRouter } from 'vue-router' 4 | 5 | export function useRouterLinks () { 6 | const router = useRouter() 7 | 8 | window.addEventListener( 9 | 'click', 10 | (e) => { 11 | if (e.defaultPrevented) return 12 | const link = (e.target as Element).closest('a') 13 | if (link) { 14 | const { protocol, hostname, pathname, hash, target } = link 15 | const currentUrl = window.location 16 | const extMatch = pathname.match(/\.\w+$/) 17 | // only intercept inbound links 18 | if ( 19 | !e.ctrlKey 20 | && !e.shiftKey 21 | && !e.altKey 22 | && !e.metaKey 23 | && target !== '_blank' 24 | && protocol === currentUrl.protocol 25 | && hostname === currentUrl.hostname 26 | && !(extMatch && extMatch[0] !== '.html') 27 | && router.resolve({ path: pathname })?.name !== 'NotFound' 28 | ) { 29 | if (pathname !== currentUrl.pathname || !hash) e.preventDefault() 30 | if (pathname !== currentUrl.pathname) router.push({ path: pathname, hash }) 31 | } 32 | } 33 | }, 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/head.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import type { AppContext, HeadConfig } from '../shared' 3 | 4 | function notEmpty (val: T | boolean | undefined | null): val is T { 5 | return Boolean(val) 6 | } 7 | 8 | export function defaultHead ({ frontmatter, meta, route, config, site }: AppContext, includeSocialTags: boolean | undefined): HeadConfig { 9 | const title = computed(() => { 10 | const title = frontmatter.title ?? meta.title 11 | return title ? `${title} · ${site.title}` : site.title 12 | }) 13 | 14 | const description = computed(() => 15 | frontmatter.description || site.description) 16 | 17 | const currentUrl = computed(() => `${site.url}${route.path}`) 18 | 19 | const metaTags: HeadConfig['meta'] = [ 20 | { charset: 'UTF-8' }, 21 | { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }, 22 | { name: 'description', content: description }, 23 | ] 24 | 25 | if (includeSocialTags !== false) { 26 | metaTags.push( 27 | { property: 'og:url', content: currentUrl }, 28 | { property: 'og:site_name', content: site.title }, 29 | { property: 'og:title', content: title }, 30 | { property: 'og:description', content: description }, 31 | { property: 'twitter:domain', content: site.canonical }, 32 | { property: 'twitter:title', content: title }, 33 | { property: 'twitter:description', content: description }, 34 | { property: 'twitter:url', content: currentUrl }, 35 | ) 36 | } 37 | 38 | return { 39 | title, 40 | meta: metaTags, 41 | link: [ 42 | config.sitemap && { rel: 'sitemap', href: `${site.url}/sitemap.xml` }, 43 | ].filter(notEmpty), 44 | htmlAttrs: { lang: 'en-US' }, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/hydration.ts: -------------------------------------------------------------------------------- 1 | import { useSSRContext } from 'vue' 2 | import { 3 | hydrateWhenIdle, 4 | hydrateNow, 5 | hydrateOnMediaQuery, 6 | hydrateWhenVisible, 7 | } from '@islands/hydration' 8 | 9 | export function newHydrationId () { 10 | if (import.meta.env.SSR) { 11 | const context = useSSRContext() 12 | context!.hydrationSerialNumber ||= 1 13 | return `ile-${context!.hydrationSerialNumber++}` 14 | } 15 | else if (import.meta.env.DEV) { 16 | return (window as any).__ILE_DEVTOOLS__.nextIslandId() 17 | } 18 | } 19 | 20 | export enum Hydrate { 21 | WhenIdle = 'client:idle', 22 | OnLoad = 'client:load', 23 | MediaQuery = 'client:media', 24 | SkipPrerender = 'client:only', 25 | WhenVisible = 'client:visible', 26 | None = 'client:none', 27 | } 28 | 29 | export const hydrationFns = { 30 | [Hydrate.WhenIdle]: hydrateWhenIdle.name, 31 | [Hydrate.OnLoad]: hydrateNow.name, 32 | [Hydrate.MediaQuery]: hydrateOnMediaQuery.name, 33 | [Hydrate.SkipPrerender]: hydrateNow.name, 34 | [Hydrate.WhenVisible]: hydrateWhenVisible.name, 35 | [Hydrate.None]: hydrateNow.name, 36 | } 37 | 38 | // Internal: Strategies that will hydrate instantly and don't need dynamic imports. 39 | export function isEager (strategy: string) { 40 | return strategy === Hydrate.OnLoad || strategy === Hydrate.SkipPrerender || strategy === Hydrate.None 41 | } 42 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/layout.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocationNormalizedLoaded } from 'vue-router' 2 | import { shallowRef } from 'vue' 3 | import { pageFromRoute } from './composables/pageData' 4 | 5 | export async function resolveLayout (route: RouteLocationNormalizedLoaded) { 6 | const page = pageFromRoute(route) 7 | try { 8 | const layout = page.layoutFn === false ? false : await page.layoutFn?.() 9 | if (route.meta.layout) 10 | route.meta.layout.value = layout 11 | else 12 | route.meta.layout = shallowRef(layout) 13 | } 14 | catch (error) { 15 | console.error(`Error while fetching '${page?.layoutName}' layout.`) 16 | throw error 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/iles/src/client/app/utils.ts: -------------------------------------------------------------------------------- 1 | export { default as serialize } from '@nuxt/devalue' 2 | 3 | export function mapObject (obj: Record, fn: (i: I, key?: string) => O): Record { 4 | const result: Record = {} 5 | for (let key in obj) 6 | result[key] = fn(obj[key], key) 7 | return result 8 | } 9 | 10 | export async function asyncMapObject (obj: Record, fn: (i: I) => Promise): Promise> { 11 | const result: Record = {} 12 | for (let key in obj) 13 | result[key] = await fn(obj[key]) 14 | return result 15 | } 16 | 17 | export function getComponentName ({ displayName, name, _componentTag, __file }: any) { 18 | return displayName || name || _componentTag || nameFromFile(__file) 19 | } 20 | 21 | function nameFromFile (file: string) { 22 | const regex = /[\\/]src(?:[\\/](?:pages|layouts))?[\\/](.*?)(?:\.vue)?$/ 23 | return file?.match(regex)?.[1] || file 24 | } 25 | -------------------------------------------------------------------------------- /packages/iles/src/client/client.d.ts: -------------------------------------------------------------------------------- 1 | import '../../types/client' 2 | -------------------------------------------------------------------------------- /packages/iles/src/client/index.ts: -------------------------------------------------------------------------------- 1 | // exports in this file are exposed to the client app via 'iles' 2 | // so the user can do `import { usePage } from 'iles'` 3 | 4 | // Generic Types 5 | export type { Router, RouteRecordRaw } from './shared' 6 | export type { VueRenderable } from './app/composables/vueRenderer' 7 | 8 | // Composables 9 | export { useAppConfig } from './app/composables/appConfig' 10 | export { usePage, computedInPage } from './app/composables/pageData' 11 | export { useMDXComponents, provideMDXComponents } from './app/composables/mdxComponents' 12 | export { useVueRenderer } from './app/composables/vueRenderer' 13 | export { useRouter, useRoute } from 'vue-router' 14 | export { useHead } from '@unhead/vue' 15 | 16 | import type { ComponentOptionsWithoutProps, ComputedRef } from 'vue' 17 | import type { UserApp, GetStaticPaths, Document } from '../../types/shared' 18 | 19 | export function useDocuments (globPattern: string): ComputedRef[]> { 20 | throw new Error(`Unresolved useDocuments('${globPattern}')`) 21 | } 22 | 23 | export function defineApp (app: UserApp) { 24 | return app 25 | } 26 | 27 | export function definePageComponent (page: ComponentOptionsWithoutProps & { getStaticPaths?: GetStaticPaths }) { 28 | return page 29 | } 30 | -------------------------------------------------------------------------------- /packages/iles/src/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "../../dist/client", 6 | "paths": { 7 | "iles": ["index.ts"] 8 | } 9 | }, 10 | "include": ["."] 11 | } 12 | -------------------------------------------------------------------------------- /packages/iles/src/client/virtual.d.ts: -------------------------------------------------------------------------------- 1 | import '../../types/virtual' 2 | -------------------------------------------------------------------------------- /packages/iles/src/node/build/build.ts: -------------------------------------------------------------------------------- 1 | import { resolveConfig } from '../config' 2 | import { renderPages } from './render' 3 | import { bundle } from './bundle' 4 | import { bundleIslands } from './islands' 5 | import { writePages } from './write' 6 | import { withSpinner, rm } from './utils' 7 | import { createSitemap } from './sitemap' 8 | 9 | export async function build (root: string) { 10 | const start = Date.now() 11 | 12 | process.env.NODE_ENV = 'production' 13 | const appConfig = await resolveConfig(root, { command: 'build', mode: 'production', isSsrBuild: false }) 14 | 15 | rm(appConfig.outDir) 16 | 17 | const bundleResult = await withSpinner('building client + server bundles', 18 | async () => await bundle(appConfig)) 19 | 20 | const islandsByPath = Object.create(null) 21 | 22 | const pagesResult = await renderPages(appConfig, islandsByPath, bundleResult) 23 | 24 | await createSitemap(appConfig, pagesResult.routesToRender) 25 | 26 | await withSpinner('building islands bundle', 27 | async () => await bundleIslands(appConfig, islandsByPath)) 28 | 29 | const ssgContext = { config: appConfig, pages: pagesResult.routesToRender } 30 | 31 | await appConfig.ssg.onSiteBundled?.(ssgContext) 32 | 33 | await withSpinner('writing pages', 34 | async () => await writePages(appConfig, islandsByPath, pagesResult)) 35 | 36 | await appConfig.ssg.onSiteRendered?.(ssgContext) 37 | 38 | rm(appConfig.tempDir) 39 | 40 | console.info(`build complete in ${((Date.now() - start) / 1000).toFixed(2)}s.`) 41 | } 42 | -------------------------------------------------------------------------------- /packages/iles/src/node/build/rebaseImports.ts: -------------------------------------------------------------------------------- 1 | import { posix } from 'path' 2 | import { init as initESLexer, parse as parseESModules } from 'es-module-lexer' 3 | import MagicString from 'magic-string' 4 | import type { AppConfig } from '../shared' 5 | 6 | export default async function rebaseImports ({ base, assetsDir }: AppConfig, codeStr: string) { 7 | const assetsBase = posix.join(base, assetsDir) 8 | try { 9 | await initESLexer 10 | const imports = parseESModules(codeStr)[0] 11 | const code = new MagicString(codeStr) 12 | imports.forEach(({ s, e, d }) => { 13 | // Skip quotes if dynamic import. 14 | if (d > -1) { 15 | s += 1 16 | e -= 1 17 | } 18 | code.overwrite(s, e, posix.join(assetsBase, code.slice(s, e)), { contentOnly: true }) 19 | }) 20 | return code.toString() 21 | } 22 | catch (error) { 23 | console.error(error) 24 | return codeStr 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/iles/src/node/build/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import { join } from 'pathe' 3 | import type { AppConfig, RouteToRender } from '../shared' 4 | import { withSpinner, warnMark } from './utils' 5 | 6 | export async function createSitemap (config: AppConfig, routesToRender: RouteToRender[]) { 7 | const { outDir, base, siteUrl, ssg: { sitemap } } = config 8 | if (!sitemap) return 9 | if (!siteUrl) return console.warn(warnMark, 'Skipping sitemap. Configure `siteUrl` to enable sitemap generation.') 10 | withSpinner('rendering sitemap', async () => { 11 | const sitemap = sitemapFor(`${siteUrl}${base}`, routesToRender) 12 | await fs.mkdir(outDir, { recursive: true }) 13 | await fs.writeFile(join(outDir, 'sitemap.xml'), sitemap, 'utf8') 14 | }) 15 | } 16 | 17 | // Internal: Create a sitemap for the rendered pages. 18 | function sitemapFor (siteUrl: string, routesToRender: RouteToRender[]) { 19 | const pageUrls = new Set() 20 | 21 | // look through built pages, only add HTML 22 | for (const route of routesToRender) { 23 | if (!route.outputFilename.endsWith('.html')) continue 24 | if (route.path === '/404') continue 25 | pageUrls.add(route.path) 26 | } 27 | 28 | const sortedUrls = Array.from(pageUrls) 29 | .sort((a, b) => a.localeCompare(b, 'en', { numeric: true })) 30 | 31 | return ` 32 | 33 | ${sortedUrls.map(url => ` ${siteUrl + url.slice(1)}\n`).join('')} 34 | 35 | ` 36 | } 37 | -------------------------------------------------------------------------------- /packages/iles/src/node/constants.ts: -------------------------------------------------------------------------------- 1 | import { version } from '../../package.json' 2 | 3 | export const ILES_APP_ENTRY = '/@iles-entry' 4 | export const VERSION = version 5 | -------------------------------------------------------------------------------- /packages/iles/src/node/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './plugin/plugin' 2 | export { ILES_APP_ENTRY } from './constants' 3 | export { build } from './build/build' 4 | export { resolveConfig } from './config' 5 | export { mergeConfig } from 'vite' 6 | import type { UserConfig } from '../../types/shared' 7 | 8 | export function defineConfig (config: UserConfig) { 9 | return config 10 | } 11 | -------------------------------------------------------------------------------- /packages/iles/src/node/modules.ts: -------------------------------------------------------------------------------- 1 | export function unwrapDefault (mod: any): T { 2 | return mod?.default ? unwrapDefault(mod.default) : mod 3 | } 4 | 5 | export function importModule (path: string): Promise { 6 | // handle modules in Windows file system 7 | if (process.platform === 'win32') { 8 | // handle D:\path\to\file 9 | if (path.match(/^\w:\\/)) 10 | return import(`file:///${path.replace(/\\/g, '/')}`).then(unwrapDefault) 11 | 12 | // handle D:/path/to/file 13 | if (path.match(/^\w:\//)) 14 | return import(`file:///${path}`).then(unwrapDefault) 15 | } 16 | 17 | return import(path).then(unwrapDefault) 18 | } 19 | -------------------------------------------------------------------------------- /packages/iles/src/node/plugin/composables.ts: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import { resolve } from 'pathe' 3 | import { uniq } from './utils' 4 | import { parseImports } from './parse' 5 | 6 | const definitionRegex = /(?:function|const|let|var)\s+(definePageComponent|use(?:Page|Route|Head|Documents)\b)/g 7 | const composableUsageRegex = /\b(definePageComponent|use(?:Page|Route|Head|Documents))\s*\(/g 8 | 9 | const composables = [ 10 | 'definePageComponent', 11 | 'useDocuments', 12 | 'useHead', 13 | 'usePage', 14 | 'useRoute', 15 | ] 16 | 17 | export async function autoImportComposables (code: string, id: string): Promise { 18 | const matches = Array.from(code.matchAll(composableUsageRegex)) 19 | if (matches.length === 0) return 20 | 21 | const imports = await parseImports(code) 22 | const defined = new Set(Array.from(code.matchAll(definitionRegex)).map(a => a[1])) 23 | 24 | const composables = uniq(matches.map(a => a[1])) 25 | .filter(composable => !defined.has(composable) && !imports[composable]) 26 | .join(', ') 27 | 28 | if (composables) 29 | return `${code}\nimport { ${composables} } from "iles"` 30 | } 31 | 32 | export function writeComposablesDTS (root: string) { 33 | fs.writeFile(resolve(root, 'composables.d.ts'), `// generated by iles 34 | // We suggest you to commit this file into source control 35 | 36 | declare global { 37 | ${composables.map(fn => ` const ${fn}: typeof import('iles')['${fn}']`).join('\n')} 38 | } 39 | 40 | export { } 41 | `, 'utf-8') 42 | } 43 | -------------------------------------------------------------------------------- /packages/iles/src/node/plugin/hmr.ts: -------------------------------------------------------------------------------- 1 | import hash from 'hash-sum' 2 | 3 | export function hmrRuntime (id: string) { 4 | const hmrId = hash(`${id}default`) 5 | return ` 6 | _sfc_main.__hmrId = "${hmrId}" 7 | __VUE_HMR_RUNTIME__.createRecord("${hmrId}", _sfc_main) 8 | import.meta.hot.accept(({default: __default}) => { 9 | __VUE_HMR_RUNTIME__.reload("${hmrId}", __default) 10 | }) 11 | ` 12 | } 13 | -------------------------------------------------------------------------------- /packages/iles/src/node/plugin/markdown.ts: -------------------------------------------------------------------------------- 1 | import type { ViteDevServer } from 'vite' 2 | import deepEqual from 'deep-equal' 3 | import type { AppConfig } from '../shared' 4 | 5 | let originalTags: string[] 6 | 7 | // Internal: Detects markdown components overriden in the app. 8 | export function detectMDXComponents (code: string, config: AppConfig, server?: ViteDevServer | undefined) { 9 | const mdxComponents = code.match(/\bmdxComponents\b(?:.*?){(.*?)}/s)?.[1] 10 | if (!mdxComponents) return 11 | 12 | const foundTags = Array.from(mdxComponents.matchAll(/\b['"]?(\w+)['"]?:/g)).map(m => m[1]) 13 | 14 | if (!originalTags) 15 | originalTags = config.markdown.overrideElements ||= [] 16 | 17 | const dynamicElements = Array.from(new Set([...originalTags, ...foundTags])).sort() 18 | if (!deepEqual(dynamicElements, config.markdown.overrideElements)) { 19 | config.markdown.overrideElements = dynamicElements 20 | server?.moduleGraph.invalidateAll() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/iles/src/node/plugin/site.ts: -------------------------------------------------------------------------------- 1 | import type { AppConfig } from '../shared' 2 | 3 | // Internal: Adds the url to the site for convenience, and enables HMR. 4 | export function extendSite (code: string, config: AppConfig) { 5 | return `${code.replace('export default ', 'let __site = ')} 6 | __site.url = '${config.siteUrl}${config.base.slice(0, config.base.length - 1)}' 7 | __site.canonical = '${config.siteUrl.split('//', 2)[1] ?? ''}' 8 | import { ref as _$ref } from 'vue' 9 | const __siteRef = _$ref(__site) 10 | __site = { ref: __siteRef } 11 | export { __site, __siteRef as default } 12 | 13 | if (import.meta.hot) 14 | import.meta.hot.accept(mod => { 15 | __site.ref.value = mod.__site.ref.value 16 | mod.__site.ref = __site.ref 17 | }) 18 | ` 19 | // NOTE: The last line replaces the ref of the current module with the ref in 20 | // the original module that was made reactive in `installPageData`, so that 21 | // subsequent HMRs are also performed as expected. 22 | } 23 | -------------------------------------------------------------------------------- /packages/iles/src/node/preview.ts: -------------------------------------------------------------------------------- 1 | import type { ServerOptions, UserConfig as ViteUserConfig } from 'vite' 2 | import { preview as vitePreview, mergeConfig } from 'vite' 3 | import { resolveConfig } from './config' 4 | 5 | export async function preview (root: string = process.cwd(), serverOptions: ServerOptions = {}) { 6 | const config = await resolveConfig(root) 7 | const viteConfig = mergeConfig(config.vite, { 8 | preview: serverOptions, 9 | } as ViteUserConfig) 10 | 11 | const server = await vitePreview(viteConfig) 12 | server.printUrls() 13 | } 14 | -------------------------------------------------------------------------------- /packages/iles/src/node/publicUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Exported sync utils should go here. 3 | * This file will be bundled to ESM and CJS and redirected by ../index.cjs 4 | * Please control the side-effects by checking the ./dist/node-cjs/publicUtils.cjs bundle 5 | */ 6 | export { VERSION as version, ILES_APP_ENTRY } from './constants' 7 | export { version as viteVersion, esbuildVersion, rollupVersion, mergeConfig } from 'vite' 8 | -------------------------------------------------------------------------------- /packages/iles/src/node/server.ts: -------------------------------------------------------------------------------- 1 | import type { ServerOptions, UserConfig as ViteUserConfig } from 'vite' 2 | import { createServer as createViteServer, mergeConfig } from 'vite' 3 | import { resolveConfig } from './config' 4 | import IslandsPlugins from './plugin/plugin' 5 | 6 | export async function createServer (root: string = process.cwd(), serverOptions: ServerOptions = {}) { 7 | const config = await resolveConfig(root) 8 | 9 | const viteConfig = mergeConfig(config.vite, { 10 | plugins: IslandsPlugins(config), 11 | server: serverOptions, 12 | } as ViteUserConfig) 13 | 14 | return { 15 | config, 16 | viteConfig, 17 | server: await createViteServer(viteConfig), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/iles/src/node/utils.ts: -------------------------------------------------------------------------------- 1 | import { basename, extname } from 'pathe' 2 | 3 | // Internal: Maps the specified path to its corresponding HTML filename. 4 | // 5 | // NOTE: `filename` can be an optional source for the specified path. 6 | export function pathToHtmlFilename (path: string, filename?: string) { 7 | const ext = extname(path) 8 | if (ext) return path 9 | if (!path.endsWith('/') && filename && basename(filename).split('.')[0] === 'index') path += '/' 10 | return path + (path.endsWith('/') ? 'index.html' : '.html') 11 | } 12 | 13 | // Internal: Used when `prettyUrls: false`. 14 | export function explicitHtmlPath (path: string, filename?: string) { 15 | const htmlFilename = pathToHtmlFilename(path, filename) 16 | return htmlFilename.endsWith('/index.html') 17 | ? htmlFilename.replace(/\/index\.html$/, '/') 18 | : htmlFilename 19 | } 20 | -------------------------------------------------------------------------------- /packages/iles/src/shared/shared.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | AppClientConfig, 3 | AppConfig, 4 | BaseIlesConfig, 5 | Awaited, 6 | ConfigEnv, 7 | CreateAppConfig, 8 | CreateAppFactory, 9 | HeadConfig, 10 | IslandDefinition, 11 | OnLoadFn, 12 | IslandsByPath, 13 | LayoutFactory, 14 | Document, 15 | NamedPlugins, 16 | PageComponent, 17 | PageData, 18 | RawPageMatter, 19 | PageFrontmatter, 20 | PageMeta, 21 | PageProps, 22 | IlesModule, 23 | IlesModuleLike, 24 | IlesModuleOption, 25 | MDXComponents, 26 | RouteMeta, 27 | Router, 28 | RouteRecordRaw, 29 | RouteLocationNormalizedLoaded, 30 | RouterOptions, 31 | AppContext, 32 | GetStaticPaths, 33 | StaticPath, 34 | SSGContext, 35 | RouteToRender, 36 | UserApp, 37 | UserSite, 38 | UserConfig, 39 | ViteOptions, 40 | PreactOptions, 41 | SolidOptions, 42 | SvelteOptions, 43 | } from '../../types/shared' 44 | -------------------------------------------------------------------------------- /packages/iles/src/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "lib": ["esnext", "dom", "dom.iterable"] 6 | }, 7 | "include": ["."] 8 | } 9 | -------------------------------------------------------------------------------- /packages/iles/tests/__snapshots__/site.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`site > with site url 1`] = ` 4 | " 5 | let __site = { 6 | title: 'îles', 7 | } 8 | 9 | __site.url = 'https://example.com/awesome' 10 | __site.canonical = 'example.com' 11 | import { ref as _$ref } from 'vue' 12 | const __siteRef = _$ref(__site) 13 | __site = { ref: __siteRef } 14 | export { __site, __siteRef as default } 15 | 16 | if (import.meta.hot) 17 | import.meta.hot.accept(mod => { 18 | __site.ref.value = mod.__site.ref.value 19 | mod.__site.ref = __site.ref 20 | }) 21 | " 22 | `; 23 | 24 | exports[`site > without data 1`] = ` 25 | " 26 | let __site = {} 27 | 28 | __site.url = 'http://example.com' 29 | __site.canonical = 'example.com' 30 | import { ref as _$ref } from 'vue' 31 | const __siteRef = _$ref(__site) 32 | __site = { ref: __siteRef } 33 | export { __site, __siteRef as default } 34 | 35 | if (import.meta.hot) 36 | import.meta.hot.accept(mod => { 37 | __site.ref.value = mod.__site.ref.value 38 | mod.__site.ref = __site.ref 39 | }) 40 | " 41 | `; 42 | 43 | exports[`site > without site url 1`] = ` 44 | " 45 | const site = { 46 | title: 'îles', 47 | } 48 | 49 | let __site = site 50 | 51 | __site.url = '' 52 | __site.canonical = '' 53 | import { ref as _$ref } from 'vue' 54 | const __siteRef = _$ref(__site) 55 | __site = { ref: __siteRef } 56 | export { __site, __siteRef as default } 57 | 58 | if (import.meta.hot) 59 | import.meta.hot.accept(mod => { 60 | __site.ref.value = mod.__site.ref.value 61 | mod.__site.ref = __site.ref 62 | }) 63 | " 64 | `; 65 | -------------------------------------------------------------------------------- /packages/iles/tests/app-config.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, describe, expect } from 'vitest' 2 | 3 | import config from '@islands/app-config' 4 | 5 | describe('app config', () => { 6 | test('site url', async () => { 7 | expect(config.root).toEqual(process.cwd()) 8 | expect(config.base).toEqual('/') 9 | expect(config.siteUrl).toEqual('https://example.com') 10 | expect(config.debug).toEqual(true) 11 | expect(config.jsx).toEqual(undefined) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /packages/iles/tests/exports.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, describe, expect } from 'vitest' 2 | 3 | import Island from '@components/Island.vue' 4 | 5 | describe('exports', () => { 6 | test('ensure island component can be resolved', () => { 7 | expect(Island.name).toEqual('Island') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/iles/tests/layouts.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, describe, expect } from 'vitest' 2 | 3 | import layout from '/src/layouts/default.vue' 4 | 5 | describe('layouts', () => { 6 | test('stub default layout', () => { 7 | expect(layout.name).toEqual('DefaultLayout') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/iles/tests/not-found.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, describe, expect } from 'vitest' 2 | 3 | import NotFound from '@islands/components/NotFound' 4 | 5 | describe('not found component', () => { 6 | test('resolves to existing component', () => { 7 | expect(NotFound.name).toEqual('NotFound') 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/iles/tests/pretty-urls.spec.ts: -------------------------------------------------------------------------------- 1 | import { toExplicitHtmlPath } from '@mdx/utils' 2 | import { test, describe, expect, beforeAll } from 'vitest' 3 | 4 | describe('prettyUrls', () => { 5 | const expectExplicitPath = (path: string) => expect(toExplicitHtmlPath(path)) 6 | 7 | test('internal urls', () => { 8 | expectExplicitPath('/about/').toEqual('/about/') 9 | expectExplicitPath('/about').toEqual('/about.html') 10 | expectExplicitPath('/about/index.html').toEqual('/about/') 11 | expectExplicitPath('/about/nested.html').toEqual('/about/nested.html') 12 | }) 13 | 14 | test('anchor tags', () => { 15 | expectExplicitPath('#contact').toEqual('#contact') 16 | 17 | expectExplicitPath('/about#contact').toEqual('/about.html#contact') 18 | expectExplicitPath('/about/index.html#contact').toEqual('/about/#contact') 19 | 20 | expectExplicitPath('https://example.com/about#contact').toEqual('https://example.com/about#contact') 21 | expectExplicitPath('https://example.com/#contact').toEqual('https://example.com/#contact') 22 | }) 23 | 24 | test('internal assets', () => { 25 | expectExplicitPath('/assets/picture.gif').toEqual('/assets/picture.gif') 26 | }) 27 | 28 | test('external urls', () => { 29 | expectExplicitPath('https://example.com').toEqual('https://example.com') 30 | expectExplicitPath('https://example.com/').toEqual('https://example.com/') 31 | expectExplicitPath('http://example.com/').toEqual('http://example.com/') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /packages/iles/tests/resolvers.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, describe, expect } from 'vitest' 2 | import path from 'path' 3 | 4 | import { IlesComponentResolver, IlesLayoutResolver } from '@node/config' 5 | import { ISLAND_COMPONENT_PATH } from '@node/alias' 6 | 7 | const projectRoot = path.resolve(__dirname, '../../..') 8 | const vuePoint = `${projectRoot}/playground/the-vue-point` 9 | 10 | describe('resolvers', () => { 11 | test('can resolve Island and Head', async () => { 12 | const resolve = IlesComponentResolver 13 | expect(resolve('Island')).toEqual({ from: ISLAND_COMPONENT_PATH }) 14 | expect(resolve('Head')).toEqual({ name: 'Head', from: '@unhead/vue/components' }) 15 | expect(resolve('Something')).toEqual(undefined) 16 | }) 17 | 18 | test('can resolve layouts', async () => { 19 | const layoutsDir = path.resolve(vuePoint, 'src/layouts') 20 | const resolve = IlesLayoutResolver({ layoutsDir }) 21 | 22 | expect(resolve('DefaultLayout')) 23 | .toEqual({ name: 'default', from: `${layoutsDir}/default.vue` }) 24 | 25 | expect(resolve('PostLayout')) 26 | .toEqual({ name: 'default', from: `${layoutsDir}/post.vue` }) 27 | 28 | expect(resolve('SomethingElseLayout')) 29 | .toEqual(undefined) 30 | 31 | expect(resolve('Layout')).toEqual(undefined) 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /packages/iles/tests/site.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, describe, expect } from 'vitest' 2 | 3 | import { extendSite } from '@node/plugin/site' 4 | 5 | describe('site', () => { 6 | test('without data', () => { 7 | const extended = extendSite(` 8 | export default {} 9 | `, { siteUrl: 'http://example.com', base: '/' }) 10 | expect(extended).toMatchSnapshot() 11 | }) 12 | 13 | test('without site url', () => { 14 | const extended = extendSite(` 15 | const site = { 16 | title: 'îles', 17 | } 18 | 19 | export default site 20 | `, { siteUrl: '', base: '/' }) 21 | expect(extended).toMatchSnapshot() 22 | }) 23 | 24 | test('with site url', () => { 25 | const extended = extendSite(` 26 | export default { 27 | title: 'îles', 28 | } 29 | `, { siteUrl: 'https://example.com', base: '/awesome/' }) 30 | expect(extended).toMatchSnapshot() 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /packages/iles/tests/user-app.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, describe, expect } from 'vitest' 2 | 3 | import emptyApp from '@islands/user-app' 4 | 5 | describe('user app', () => { 6 | test('can import empty app', () => { 7 | expect(emptyApp).toEqual({}) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/iles/tests/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, describe, expect } from 'vitest' 2 | 3 | import { pascalCase, serialize } from '@node/plugin/utils' 4 | 5 | describe('case conversions', () => { 6 | test('pascalCase', () => { 7 | expect(pascalCase('AudioPlayer')).toEqual('AudioPlayer') 8 | expect(pascalCase('audio-player')).toEqual('AudioPlayer') 9 | expect(pascalCase('bx:bx-sun')).toEqual('BxBxSun') 10 | }) 11 | }) 12 | 13 | describe('serialize', () => { 14 | test('to string', () => { 15 | const audio = '/song.mp3' 16 | const recordedAt = new Date() 17 | const value = { audio, recordedAt } 18 | const serialized = serialize(value) 19 | // eslint-disable-next-line no-new-func 20 | expect(Function(`return ${serialized}`)()).toEqual(value) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/iles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "moduleResolution": "node", 7 | "resolveJsonModule": true, 8 | "strict": true, 9 | "declaration": true, 10 | "noUnusedLocals": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "useUnknownInCatchVariables": false, 14 | "lib": ["esnext", "DOM"], 15 | "types": ["node", "vite/client", "vitest"], 16 | "paths": { 17 | "src/*": ["src/*"], 18 | "@components/*": ["src/client/app/components/*"], 19 | "@client/*": ["src/client/*"], 20 | "@node/*": ["src/node/*"], 21 | "@mdx/*": ["../mdx/src/*"], 22 | "lib/*": ["src/node/*"], 23 | "shared/*": ["src/shared/*"], 24 | "tests/*": ["__tests__/*"], 25 | "/@shared/*": ["src/client/shared/*"], 26 | "iles": ["types/index.d.ts"] 27 | } 28 | }, 29 | "include": ["src", "tests", "types"], 30 | "exclude": ["dist"] 31 | } 32 | -------------------------------------------------------------------------------- /packages/iles/tsup-cjs.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | dts: true, 4 | target: 'node20', 5 | splitting: false, 6 | sourcemap: false, 7 | format: ['cjs'], 8 | outDir: 'dist/node-cjs', 9 | external: [ 10 | '@vue/runtime-dom/dist/runtime-dom.esm-bundler.js', 11 | 'solid-js/web', 12 | 'preact', 13 | 'esbuild', 14 | 'rollup', 15 | 'vite', 16 | '@antfu/install-pkg', 17 | 'fast-glob', 18 | 'preact-render-to-string', 19 | '@vue/server-renderer', 20 | '@islands/hydration/preact', 21 | '@islands/hydration/dist/hydration.js', 22 | '@islands/hydration/dist/vue.js', 23 | '@islands/hydration/dist/vanilla.js', 24 | '@islands/hydration/dist/solid.js', 25 | '@islands/hydration/dist/preact.js', 26 | '@islands/hydration/dist/svelte.js', 27 | ], 28 | } 29 | -------------------------------------------------------------------------------- /packages/iles/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | dts: true, 4 | target: 'node20', 5 | splitting: true, 6 | sourcemap: false, 7 | format: ['esm'], 8 | outDir: 'dist/node', 9 | external: [ 10 | '@vue/runtime-dom/dist/runtime-dom.esm-bundler.js', 11 | 'solid-js/web', 12 | 'esbuild', 13 | 'rollup', 14 | 'vite', 15 | 'preact', 16 | '@antfu/install-pkg', 17 | 'fast-glob', 18 | 'preact-render-to-string', 19 | '@vue/server-renderer', 20 | '@islands/hydration/preact', 21 | '@islands/hydration/dist/hydration.js', 22 | '@islands/hydration/dist/vue.js', 23 | '@islands/hydration/dist/vanilla.js', 24 | '@islands/hydration/dist/solid.js', 25 | '@islands/hydration/dist/preact.js', 26 | '@islands/hydration/dist/svelte.js', 27 | ], 28 | } 29 | -------------------------------------------------------------------------------- /packages/iles/types/client.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | PageFrontmatter, 3 | PageMeta, 4 | UserSite, 5 | StaticPath, 6 | Router, 7 | RouteLocationNormalizedLoaded, 8 | } from './shared' 9 | 10 | declare module 'vue-router' { 11 | interface RouteMeta { 12 | layout?: import('vue').Ref 13 | pathVariants?: import('vue').Ref 14 | pathVariantsPromise?: import('vue').ComputedRef> 15 | } 16 | } 17 | 18 | declare module '@vue/runtime-core' { 19 | export interface ComponentCustomProperties { 20 | /** 21 | * The frontmatter of the current page. 22 | */ 23 | $frontmatter: PageFrontmatter 24 | /** 25 | * Information about the current page, including href and filename. 26 | */ 27 | $meta: PageMeta 28 | /** 29 | * Information about the site as exported in src/site.ts 30 | */ 31 | $site: UserSite 32 | /** 33 | * Normalized current location. See {@link RouteLocationNormalizedLoaded}. 34 | */ 35 | $route: RouteLocationNormalizedLoaded 36 | /** 37 | * {@link Router} instance used by the application. 38 | */ 39 | $router: Router 40 | } 41 | } 42 | 43 | declare global { 44 | interface Window {} 45 | } 46 | -------------------------------------------------------------------------------- /packages/iles/types/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Plugin from '../dist/node/plugin/plugin' 4 | 5 | export default Plugin 6 | export * from './shared' 7 | export * from '../dist/client/index' 8 | export * from '../dist/node/index' 9 | 10 | import 'vue-router' 11 | import './virtual' 12 | import './client' 13 | -------------------------------------------------------------------------------- /packages/iles/types/virtual.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | const comp: import('./shared').PageComponent 3 | export default comp 4 | } 5 | 6 | declare module '*.mdx' { 7 | const comp: import('./shared').PageComponent 8 | export default comp 9 | } 10 | 11 | declare module '@islands/routes' { 12 | import type { RouteRecordRaw } from 'vue-router' 13 | const routes: RouteRecordRaw[] 14 | export default routes 15 | } 16 | 17 | declare module '@islands/app-config' { 18 | const config: import('./shared').AppClientConfig 19 | export default config 20 | } 21 | 22 | declare module '@islands/user-app' { 23 | const config: import('./shared').UserApp 24 | export default config 25 | } 26 | 27 | declare module '@islands/user-site' { 28 | const config: import('vue').Ref 29 | export default config 30 | } 31 | 32 | declare module '@islands/components/NotFound' { 33 | const component: import('vue').Component 34 | export default component 35 | } 36 | -------------------------------------------------------------------------------- /packages/images/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/images", 3 | "version": "0.10.0-beta.1", 4 | "scripts": { 5 | "dev": "npm run build -- --watch", 6 | "build": "tsup src/images.ts", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix" 9 | }, 10 | "type": "module", 11 | "files": [ 12 | "dist", 13 | "src/Picture.vue" 14 | ], 15 | "types": "dist/images.d.ts", 16 | "exports": { 17 | ".": { 18 | "import": "./dist/images.js", 19 | "require": "./dist/images.cjs", 20 | "types": "./dist/images.d.ts" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "funding": "https://github.com/sponsors/ElMassimo", 25 | "author": "Máximo Mussini ", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/ElMassimo/iles" 29 | }, 30 | "homepage": "https://github.com/ElMassimo/iles", 31 | "bugs": "https://github.com/ElMassimo/iles/issues", 32 | "dependencies": { 33 | "pathe": "^1.1.2", 34 | "vite-plugin-image-presets": "^0.3.4" 35 | }, 36 | "devDependencies": { 37 | "iles": "workspace:*", 38 | "tsup": "8.2.4", 39 | "typescript": "^5.6.3" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/images/src/Picture.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /packages/images/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | format: ['esm', 'cjs'], 7 | } 8 | -------------------------------------------------------------------------------- /packages/mdx/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

@islands/mdx

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | [docs]: https://iles-docs.netlify.app 23 | [MDX]: https://github.com/mdx-js/mdx 24 | [frontmatter]: https://iles-docs.netlify.app/guide/markdown#frontmatter-and-meta 25 | [mdx documents]: https://iles-docs.netlify.app/guide/markdown 26 | [resolveComponent]: https://v3.vuejs.org/api/global-api.html#resolvecomponent 27 | [unplugin-vue-components]: https://github.com/antfu/unplugin-vue-components 28 | 29 | An [îles] module that adds support for [MDX documents], powered by [MDX]. 30 | 31 | It also injects a [recma][MDX] plugin to resolve Vue components in [MDX documents]: 32 | 33 | - 🌎 you can use globally registered components 34 | - 🧱 [`unplugin-vue-components`][unplugin-vue-components] can statically resolve and import components, so you don't need to manually provide components 35 | - 📝 exposes data from plugins to [`meta`][mdx documents] 36 | -------------------------------------------------------------------------------- /packages/mdx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/mdx", 3 | "version": "0.10.0-beta.1", 4 | "scripts": { 5 | "dev": "npm run build -- --watch", 6 | "build": "tsup src/mdx.ts", 7 | "tsc": "tsc src/mdx.ts --noEmit --skipLibCheck", 8 | "lint": "eslint .", 9 | "lint:fix": "eslint . --fix" 10 | }, 11 | "type": "module", 12 | "files": [ 13 | "dist", 14 | "src" 15 | ], 16 | "types": "dist/mdx.d.ts", 17 | "main": "dist/mdx.js", 18 | "exports": { 19 | ".": { 20 | "import": "./dist/mdx.js", 21 | "require": "./src/mdx.cjs" 22 | }, 23 | "./package.json": "./package.json" 24 | }, 25 | "funding": "https://github.com/sponsors/ElMassimo", 26 | "author": "Máximo Mussini ", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/ElMassimo/iles" 30 | }, 31 | "homepage": "https://github.com/ElMassimo/iles", 32 | "bugs": "https://github.com/ElMassimo/iles/issues", 33 | "dependencies": { 34 | "@mdx-js/mdx": "3.0.1", 35 | "estree-walker": "^3.0", 36 | "hash-sum": "^2.0", 37 | "hast-util-to-html": "^9.0.1", 38 | "remark-frontmatter": "^5.0.0", 39 | "source-map": "^0.7.4", 40 | "unist-util-visit": "^5.0.0" 41 | }, 42 | "devDependencies": { 43 | "@types/estree-jsx": "^1.0.5", 44 | "@types/hash-sum": "^1.0", 45 | "@types/hast": "^3.0.4", 46 | "hast-util-raw": "^9.0.4", 47 | "mdast-util-mdx-expression": "^2.0.1", 48 | "tsup": "8.2.4", 49 | "typescript": "^5.6.3", 50 | "unified": "^11.0.5", 51 | "vfile": "^6.0.2", 52 | "vite": "^5.4.10" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/mdx/src/mdx.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (...args) => new Promise((resolve, reject) => { 2 | import('../dist/mdx.js') 3 | .then(m => resolve(m.default(...args))) 4 | .catch(reject) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/mdx/src/mdx.ts: -------------------------------------------------------------------------------- 1 | import remarkFrontmatter from 'remark-frontmatter' 2 | 3 | import type { VFile } from 'vfile' 4 | import recmaPlugin from './recma-plugin' 5 | import mdxPlugins from './mdx-vite-plugins' 6 | import { remarkInternalHrefs } from './remark-internal-hrefs' 7 | import { remarkMdxImages } from './remark-mdx-images' 8 | import { rehypeRawExpressions } from './rehype-raw-expressions' 9 | 10 | /** 11 | * An iles module that injects a recma plugin that transforms MDX to allow 12 | * resolving Vue components statically or at runtime. 13 | */ 14 | export function vueMdx (): any { 15 | return { 16 | name: '@islands/mdx', 17 | markdown: { 18 | recmaPlugins: [recmaPlugin], 19 | }, 20 | configResolved (config: any) { 21 | const { markdown, prettyUrls, namedPlugins } = config 22 | 23 | markdown.remarkPlugins.unshift( 24 | [remarkFrontmatter, ['yaml', 'toml']], 25 | [remarkMdxImages, markdown], 26 | [remarkInternalHrefs, { prettyUrls }], 27 | ) 28 | 29 | markdown.rehypePlugins.push( 30 | [rehypeRawExpressions, markdown], 31 | ) 32 | 33 | markdown.recmaPlugins.push( 34 | // NOTE: Expose VFile data added by remark and rehype plugins. 35 | () => (_ast: any, vfile: VFile) => { 36 | const page = namedPlugins.pages.api.pageForFilename(vfile.path) 37 | if (page) Object.assign(page.frontmatter.meta, vfile.data) 38 | }, 39 | ) 40 | 41 | config.vitePlugins.push(...mdxPlugins(markdown)) 42 | }, 43 | } 44 | } 45 | 46 | export { vueMdx as default, recmaPlugin } 47 | export * from './types' 48 | -------------------------------------------------------------------------------- /packages/mdx/src/plugins.ts: -------------------------------------------------------------------------------- 1 | import type { Pluggable } from 'unified' 2 | import type { PluginLike, PluginOption } from './types' 3 | 4 | // Resolve plugins that might need an async import in CJS. 5 | export async function resolvePlugins (plugins: PluginOption[]) { 6 | return compact(await Promise.all(plugins.map(resolvePlugin))) 7 | } 8 | 9 | async function resolvePlugin (plugin: PluginOption): Promise { 10 | if (isString(plugin)) return await importPlugin(plugin) 11 | if (!plugin) return plugin 12 | if (isStringPlugin(plugin)) return await importPlugin(...plugin) 13 | return plugin 14 | } 15 | 16 | async function importPlugin (pkgName: string, ...options: any[]): Promise { 17 | return [await import(pkgName).then(unwrapModule), ...options] 18 | } 19 | 20 | function unwrapModule (mod: any): any { 21 | return mod && mod.default ? unwrapModule(mod.default) : mod 22 | } 23 | 24 | export function isString (val: any): val is string { 25 | return typeof val === 'string' 26 | } 27 | 28 | export function isStringPlugin (val: any): val is [string, any] { 29 | return Array.isArray(val) && isString(val[0]) 30 | } 31 | 32 | export function compact (val: (false | undefined | null | T)[]): T[] { 33 | return val.filter(x => x) as T[] 34 | } 35 | -------------------------------------------------------------------------------- /packages/mdx/src/remark-internal-hrefs.ts: -------------------------------------------------------------------------------- 1 | import type { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx' 2 | import type { Root } from 'mdast' 3 | import type { Plugin, Transformer } from 'unified' 4 | 5 | import { visit, SKIP } from 'unist-util-visit' 6 | 7 | import { isJsxElement, isString, toExplicitHtmlPath } from './utils' 8 | 9 | export interface HrefOptions { 10 | prettyUrls?: boolean 11 | } 12 | 13 | type HrefPlugin = Plugin<[HrefOptions?], Root, Root> 14 | type HrefProcessor = Transformer 15 | 16 | /** 17 | * A Remark plugin for converting Markdown images to Mdx images using imports 18 | * for the image source. 19 | */ 20 | export const remarkInternalHrefs: HrefPlugin = (options) => { 21 | if (!options?.prettyUrls) return remarkProcessor 22 | } 23 | 24 | const remarkProcessor: HrefProcessor = (ast, vfile) => { 25 | visit(ast, (node) => { 26 | if (node.type === 'link') { 27 | const { url } = node 28 | if (url) node.url = toExplicitHtmlPath(url) 29 | return SKIP 30 | } 31 | 32 | if (isJsxElement(node) && (node.name === 'a' || node.name === 'Link')) { 33 | replaceHrefAttribute(node) 34 | return SKIP 35 | } 36 | }) 37 | 38 | function replaceHrefAttribute (node: MdxJsxTextElement | MdxJsxFlowElement) { 39 | for (const attr of node.attributes) { 40 | if (attr.type === 'mdxJsxAttribute' && attr.name === 'href') { 41 | if (isString(attr.value) && attr.value) attr.value = toExplicitHtmlPath(attr.value) 42 | break 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/mdx/src/types.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | import type { CompileOptions } from '@mdx-js/mdx' 5 | import type { Pluggable } from 'unified' 6 | import type { VFile } from 'vfile' 7 | 8 | export type PluginLike = null | undefined | false | Pluggable 9 | export type PluginOption = PluginLike | Promise | string | [string, any] 10 | 11 | export interface MarkdownOptions extends Omit { 12 | /** 13 | * Recma plugins that should be used to process files. 14 | */ 15 | recmaPlugins?: PluginOption[] 16 | 17 | /** 18 | * Remark plugins that should be used to process files. 19 | */ 20 | remarkPlugins?: PluginOption[] 21 | 22 | /** 23 | * Rehype plugins that should be used to process files. 24 | */ 25 | rehypePlugins?: PluginOption[] 26 | 27 | /** 28 | * Allows to modify an image src. Useful to customize image processing using 29 | * `vite-imagetools` or other rollup plugins. 30 | */ 31 | withImageSrc?: (src: string, file: VFile) => string | void 32 | 33 | /** 34 | * Built-in tags that should not be optimized as HTML. 35 | */ 36 | overrideElements?: string[] 37 | } 38 | -------------------------------------------------------------------------------- /packages/mdx/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { extname } from 'path' 2 | import type { Node } from 'unist' 3 | import type { MdxJsxTextElement, MdxJsxFlowElement } from 'mdast-util-mdx-jsx' 4 | 5 | const urlPattern = /^(https?:)?\// 6 | const externalUrlPattern = /^(https?:)?\/\// 7 | 8 | export function isAbsolute (url: string) { 9 | return urlPattern.test(url) 10 | } 11 | 12 | export function isExternal (url: string) { 13 | return externalUrlPattern.test(url) 14 | } 15 | 16 | export function isJsxElement (node: Node): node is MdxJsxTextElement | MdxJsxFlowElement { 17 | return node.type === 'mdxJsxTextElement' || node.type === 'mdxJsxFlowElement' 18 | } 19 | 20 | export function isString (val: any): val is string { 21 | return typeof val === 'string' 22 | } 23 | 24 | export function toExplicitHtmlPath (url: string) { 25 | if (isExternal(url)) return url 26 | 27 | let [path, anchor] = url.split('#', 2) 28 | if (path === '' || path.endsWith('/')) return url 29 | 30 | const ext = extname(path) 31 | if (ext && ext !== '.html') return url 32 | 33 | path = path.endsWith('.html') ? path.replace(/\/index\.html$/, '/') : `${path}.html` 34 | return anchor ? `${path}#${anchor}` : path 35 | } 36 | -------------------------------------------------------------------------------- /packages/mdx/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | splitting: true, 7 | format: ['esm'], 8 | } 9 | -------------------------------------------------------------------------------- /packages/pages/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

@islands/pages

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | [docs]: https://iles-docs.netlify.app 23 | [pages]: https://iles-docs.netlify.app/guide/development#pages 24 | [frontmatter]: /guide/markdown#frontmatter-and-meta 25 | [routing]: https://iles-docs.netlify.app/guide/routing 26 | [vite-plugin-pages]: https://github.com/hannoeru/vite-plugin-pages 27 | 28 | An [îles] module that provides support for [pages], inspired by [vite-plugin-pages]. 29 | 30 | - 🛣 file-based [routing] 31 | - 🎣 hooks to extend [frontmatter] and route data 32 | - 📄 adds support for a [`` block][pages] in Vue single-file components 33 | 34 | ```ts 35 | extendFrontmatter (frontmatter, filename) { 36 | if (filename.includes('/posts/')) 37 | frontmatter.layout = 'post' 38 | }, 39 | extendRoute (route) { 40 | if (route.path.startsWith('/posts')) 41 | route.path = path.replace(/[\d-]+/, '') // remove date 42 | }, 43 | extendRoutes (routes) { 44 | routes.push({ path: '/custom', name: 'Custom', componentFilename: ... })) 45 | }, 46 | ``` 47 | 48 | extendFrontmatter is very flexible, you could use it to: 49 | 50 | - Infer the title or date from the filename 51 | - Set a different layout for all pages in a specific dir 52 | - Provide additional data to use in the page, such as `gitLastUpdated` 53 | 54 | ## Acknowledgements 55 | 56 | - [`vite-plugin-pages`][vite-plugin-pages]: Early versions of îles used this wonderful library 57 | -------------------------------------------------------------------------------- /packages/pages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/pages", 3 | "version": "0.10.0-beta.1", 4 | "scripts": { 5 | "dev": "npm run build -- --watch", 6 | "build": "tsup src/pages.ts", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix" 9 | }, 10 | "type": "module", 11 | "files": [ 12 | "dist", 13 | "src" 14 | ], 15 | "types": "dist/pages.d.ts", 16 | "main": "dist/pages.js", 17 | "exports": { 18 | ".": { 19 | "import": "./dist/pages.js", 20 | "require": "./src/pages.cjs" 21 | }, 22 | "./package.json": "./package.json" 23 | }, 24 | "funding": "https://github.com/sponsors/ElMassimo", 25 | "author": "Máximo Mussini ", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/ElMassimo/iles" 29 | }, 30 | "homepage": "https://github.com/ElMassimo/iles", 31 | "bugs": "https://github.com/ElMassimo/iles/issues", 32 | "dependencies": { 33 | "debug": "^4.3.5", 34 | "deep-equal": "^2.2.3", 35 | "fast-glob": "^3.3.2", 36 | "gray-matter": "^4.0.3", 37 | "pathe": "^1.1.2" 38 | }, 39 | "peerDependencies": { 40 | "vue": "^3.3.4" 41 | }, 42 | "devDependencies": { 43 | "@types/deep-equal": "^1.0.4", 44 | "@types/js-yaml": "^4.0.9", 45 | "tsup": "8.2.4", 46 | "typescript": "^5.6.3", 47 | "unified": "^11.0.5", 48 | "vue": "^3.5.12" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/pages/src/hmr.ts: -------------------------------------------------------------------------------- 1 | import type { ViteDevServer, Plugin } from 'vite' 2 | import { debug, slash } from './utils' 3 | import { Awaitable, MODULE_ID, ResolvedOptions, PagesApi } from './types' 4 | 5 | export function handleHMR (api: PagesApi, options: ResolvedOptions, clearRoutes: () => void): Plugin['handleHotUpdate'] { 6 | const server = options.server! 7 | 8 | onPage('add', async (path) => { 9 | const page = await api.addPage(path) 10 | debug.hmr('add %s %O', path, page) 11 | return true 12 | }) 13 | 14 | onPage('unlink', (path) => { 15 | api.removePage(path) 16 | debug.hmr('remove', path) 17 | return true 18 | }) 19 | 20 | return async (ctx) => { 21 | const path = slash(ctx.file) 22 | if (api.isPage(path)) { 23 | const { changed, needsReload } = await api.updatePage(path) 24 | if (changed) debug.hmr('change', path) 25 | if (needsReload) fullReload() 26 | } 27 | } 28 | 29 | function onPage (eventName: string, handler: (path: string) => Awaitable) { 30 | server.watcher.on(eventName, async (path) => { 31 | path = slash(path) 32 | if (api.isPage(path) && await handler(path)) 33 | fullReload() 34 | }) 35 | } 36 | 37 | function fullReload () { 38 | invalidatePagesModule(server) 39 | clearRoutes() 40 | server.ws.send({ type: 'full-reload' }) 41 | } 42 | } 43 | 44 | function invalidatePageFiles (path: string, { moduleGraph }: ViteDevServer) { 45 | moduleGraph.getModulesByFile(path) 46 | ?.forEach(mod => moduleGraph.invalidateModule(mod)) 47 | } 48 | 49 | function invalidatePagesModule ({ moduleGraph }: ViteDevServer) { 50 | const mod = moduleGraph.getModuleById(MODULE_ID) 51 | if (mod) moduleGraph.invalidateModule(mod) 52 | } 53 | -------------------------------------------------------------------------------- /packages/pages/src/pages.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (...args) => new Promise((resolve, reject) => { 2 | import('../dist/pages.js') 3 | .then(m => resolve(m.default(...args))) 4 | .catch(reject) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/pages/src/utils.ts: -------------------------------------------------------------------------------- 1 | import Debug from 'debug' 2 | 3 | export const debug = { 4 | hmr: Debug('iles:pages:hmr'), 5 | } 6 | 7 | export function slash (path: string): string { 8 | return path.replace(/\\/g, '/') 9 | } 10 | -------------------------------------------------------------------------------- /packages/pages/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | splitting: true, 7 | format: ['esm'], 8 | } 9 | -------------------------------------------------------------------------------- /packages/prerender/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

@islands/prerender

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | 23 | Internal utility for [îles] to prerender Vue, Svelte, Preact, and Solid components. 24 | -------------------------------------------------------------------------------- /packages/prerender/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/prerender", 3 | "description": "Prerender utilities for îles", 4 | "version": "0.10.0-beta.1", 5 | "type": "module", 6 | "files": [ 7 | "dist" 8 | ], 9 | "types": "dist/prerender.d.ts", 10 | "module": "dist/prerender.js", 11 | "main": "dist/prerender.js", 12 | "scripts": { 13 | "dev": "npm run build -- --watch", 14 | "build": "tsup prerender.ts", 15 | "lint": "eslint .", 16 | "lint:fix": "eslint . --fix" 17 | }, 18 | "exports": { 19 | ".": "./dist/prerender.js", 20 | "./package.json": "./package.json" 21 | }, 22 | "keywords": [ 23 | "vite", 24 | "vue", 25 | "islands", 26 | "ssg" 27 | ], 28 | "author": "Máximo Mussini", 29 | "license": "MIT", 30 | "homepage": "https://github.com/ElMassimo/iles", 31 | "bugs": { 32 | "url": "https://github.com/ElMassimo/iles/issues" 33 | }, 34 | "dependencies": { 35 | "@islands/hydration": "workspace:^0.10.0-beta.1" 36 | }, 37 | "devDependencies": { 38 | "preact": "^10.24.3", 39 | "preact-render-to-string": "^6.5.11", 40 | "solid-js": "^1.9.3", 41 | "svelte": "^5.1.13", 42 | "tsup": "8.2.4", 43 | "typescript": "^5.6.3", 44 | "vite": "^5.4.10", 45 | "vue": "^3.5.12" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/prerender/prerender.ts: -------------------------------------------------------------------------------- 1 | import type { Props, Slots, Framework } from '@islands/hydration' 2 | 3 | export type { Framework } 4 | 5 | export type PrerenderFn = 6 | (component: any, props: Props, slots: Slots | undefined, id: string) => Promise 7 | 8 | const _imports: { 9 | preact?: [ 10 | typeof import('@islands/hydration/preact'), 11 | typeof import('preact-render-to-string'), 12 | ] 13 | solid?: typeof import('solid-js/web') 14 | } = {} 15 | 16 | export const renderers: Record = { 17 | async preact (component, props, slots) { 18 | const [ 19 | { createElement }, 20 | { renderToString }, 21 | ] = _imports.preact ||= await Promise.all([ 22 | import('@islands/hydration/preact'), 23 | import('preact-render-to-string'), 24 | ]) 25 | const node = createElement(component, props, slots) 26 | return renderToString(node as any) 27 | }, 28 | async solid (component, props, slots, renderId) { 29 | const { ssr, renderToString, createComponent } = _imports.solid ||= await import('solid-js/web') 30 | return renderToString(() => { 31 | const children = slots?.default && ssr(slots.default) 32 | return createComponent(component, { ...props, children }) 33 | }, { renderId }) 34 | }, 35 | async svelte (component, props, slots, renderId) { 36 | const renderSvelteComponent = (await import('./svelte')).default 37 | return renderSvelteComponent(component, props, slots, renderId) 38 | }, 39 | async vanilla () { 40 | throw new Error('The vanilla strategy does not prerender islands.') 41 | }, 42 | async vue () { 43 | throw new Error('The vue strategy prerenders islands directly in the main app.') 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /packages/prerender/svelte.ts: -------------------------------------------------------------------------------- 1 | import { createRawSnippet, type Snippet } from 'svelte' 2 | import { render } from 'svelte/server' 3 | import type { PrerenderFn } from './prerender' 4 | 5 | const renderSvelteComponent: PrerenderFn = async (Component, props, slots, _id) => { 6 | let children; 7 | let $$slots: Record & { default?: boolean } | undefined 8 | let renderFns: Record = {} 9 | 10 | slots && Object.entries(slots).forEach(([slotName, html]) => { 11 | const fnName = slotName === 'default' ? 'children' : slotName 12 | renderFns[fnName] = createRawSnippet(() => ({ render: () => html })) 13 | 14 | $$slots ??= {} 15 | if (slotName === 'default') { 16 | $$slots.default = true 17 | children = renderFns[fnName] 18 | } 19 | else { 20 | $$slots[fnName] = renderFns[fnName] 21 | } 22 | }) 23 | 24 | return render(Component, { 25 | props: { 26 | ...props, 27 | children, 28 | $$slots, 29 | ...renderFns, 30 | }, 31 | }).body 32 | } 33 | 34 | export default renderSvelteComponent 35 | -------------------------------------------------------------------------------- /packages/prerender/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": ["esnext", "DOM"], 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "strictNullChecks": true, 9 | "moduleResolution": "Node", 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "noUnusedParameters": true 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | "examples", 17 | "dist" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/prerender/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | format: ['esm'], 7 | external: [ 8 | 'vue', 9 | 'preact', 10 | '@islands/hydration/preact', 11 | 'preact-render-to-string', 12 | 'solid-js', 13 | 'solid-js/web', 14 | 'solid-js/web/dist/web.js', 15 | 'solid-js/web/dist/server.js', 16 | 'svelte', 17 | 'svelte/server', 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /packages/prism/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [0.10.0-beta.1](https://github.com/ElMassimo/iles/compare/prism@0.8.0...prism@0.10.0-beta.1) (2024-12-04) 2 | 3 | 4 | ### Features 5 | 6 | * update dependencies (latest vite) ([#281](https://github.com/ElMassimo/iles/issues/281)) ([c291852](https://github.com/ElMassimo/iles/commit/c29185255e41e63830236ceb4c67de599aae2012)) 7 | 8 | 9 | 10 | # [0.8.0](https://github.com/ElMassimo/iles/compare/prism@0.1.1...prism@0.8.0) (2022-07-14) 11 | 12 | 13 | ### Features 14 | 15 | * convert to ESM and add support for Vite 3 ([#147](https://github.com/ElMassimo/iles/issues/147)) ([7e397b9](https://github.com/ElMassimo/iles/commit/7e397b908746cd8ec875da2a636ae667ae98cb30)) 16 | 17 | 18 | 19 | ## [0.1.1](https://github.com/ElMassimo/iles/compare/prism@0.1.0...prism@0.1.1) (2021-12-28) 20 | 21 | 22 | ### Features 23 | 24 | * hoist static MDX content instead of creating vnodes ([#66](https://github.com/ElMassimo/iles/issues/66)) ([07a7a36](https://github.com/ElMassimo/iles/commit/07a7a36430c6d97792910e346409027dfe10909b)) 25 | 26 | 27 | # 0.1.0 (2021-12-01) 28 | 29 | 30 | ### Features 31 | 32 | * new @islands/prism module with code and line highlighting ([f7416ec](https://github.com/ElMassimo/iles/commit/f7416ec8ea45b10fd199bdb2806ea54373ec2bf9)) 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/prism/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/prism", 3 | "version": "0.10.0-beta.1", 4 | "scripts": { 5 | "dev": "npm run build -- --watch", 6 | "build": "tsup src/prism.ts", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix" 9 | }, 10 | "type": "module", 11 | "files": [ 12 | "dist", 13 | "src" 14 | ], 15 | "types": "dist/prism.d.ts", 16 | "exports": { 17 | ".": { 18 | "import": "./dist/prism.js", 19 | "require": "./src/prism.cjs" 20 | }, 21 | "./package.json": "./package.json" 22 | }, 23 | "funding": "https://github.com/sponsors/ElMassimo", 24 | "author": "Máximo Mussini ", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/ElMassimo/iles" 28 | }, 29 | "homepage": "https://github.com/ElMassimo/iles", 30 | "bugs": "https://github.com/ElMassimo/iles/issues", 31 | "dependencies": { 32 | "prismjs": "^1.29.0", 33 | "unist-util-visit": "^5.0.0" 34 | }, 35 | "devDependencies": { 36 | "@types/mdast": "^4.0.4", 37 | "@types/prismjs": "^1.26.4", 38 | "iles": "workspace:*", 39 | "tsup": "8.2.4", 40 | "typescript": "^5.6.3", 41 | "unified": "^11.0.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/prism/src/prism.cjs: -------------------------------------------------------------------------------- 1 | module.exports = (...args) => new Promise((resolve, reject) => { 2 | import('../dist/prism.js') 3 | .then(m => resolve(m.default(...args))) 4 | .catch(reject) 5 | }) 6 | -------------------------------------------------------------------------------- /packages/prism/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | format: ['esm'], 7 | } 8 | -------------------------------------------------------------------------------- /packages/pwa/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | 9 | 10 | 17 | 18 |
11 |
12 |

13 |

@islands/pwa

14 | 15 |

16 |
19 |

20 | 21 | [îles]: https://github.com/ElMassimo/iles 22 | [pwa]: https://iles-docs.netlify.app/guide/pwa 23 | [vite-plugin-pwa]: https://github.com/antfu/vite-plugin-pwa 24 | 25 | An [îles] module to add and configure [vite-plugin-pwa], created by @userquin. 26 | 27 | ### Usage 🚀 28 | 29 | See the [_PWA guide_][pwa] for more information. 30 | 31 | ```ts 32 | // iles.config.ts 33 | import { defineConfig } from 'iles' 34 | import pwa from '@islands/pwa' 35 | 36 | export default defineConfig({ 37 | modules: [ 38 | pwa(options), 39 | ], 40 | }) 41 | ``` 42 | -------------------------------------------------------------------------------- /packages/pwa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@islands/pwa", 3 | "version": "0.10.0-beta.1", 4 | "scripts": { 5 | "dev": "npm run build -- --watch", 6 | "build": "tsup src/pwa.ts", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix" 9 | }, 10 | "files": [ 11 | "dist", 12 | "src" 13 | ], 14 | "types": "dist/pwa.d.cts", 15 | "type": "module", 16 | "exports": { 17 | ".": { 18 | "import": "./src/pwa.mjs", 19 | "require": "./dist/pwa.cjs" 20 | }, 21 | "./package.json": "./package.json" 22 | }, 23 | "funding": "https://github.com/sponsors/userquin", 24 | "authors": [ 25 | "Joaquín Sánchez " 26 | ], 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/ElMassimo/iles" 30 | }, 31 | "homepage": "https://github.com/ElMassimo/iles", 32 | "bugs": "https://github.com/ElMassimo/iles/issues", 33 | "dependencies": { 34 | "vite-plugin-pwa": "^0.20.5" 35 | }, 36 | "devDependencies": { 37 | "iles": "workspace:*" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/pwa/src/pwa.mjs: -------------------------------------------------------------------------------- 1 | import mod from '../dist/pwa.cjs' 2 | 3 | export default mod.default 4 | -------------------------------------------------------------------------------- /packages/pwa/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup' 2 | export const tsup: Options = { 3 | clean: true, 4 | dts: true, 5 | target: 'node20', 6 | splitting: true, 7 | format: ['cjs'], 8 | } 9 | -------------------------------------------------------------------------------- /playground/the-vue-point/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .vitepress/metadata.json 4 | .iles-ssg-temp 5 | -------------------------------------------------------------------------------- /playground/the-vue-point/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

8 | îles — french word for "islands" 9 |

10 | 11 |

Islands of interactivity with Vue in Vite.js

12 | 13 |

14 | 15 | 16 | 17 | 18 | License 19 | 20 |

21 | 22 |
23 | 24 | [demo]: https://the-vue-point-with-iles.netlify.app/ 25 | [Vue.js official blog]: http://blog.vuejs.org/ 26 | 27 | # The Vue Point - with îles 28 | 29 | This is the source code of [the îles port][demo] of the [Vue.js official blog]. 30 | 31 | Since it doesn't contain any interactive components, it ships __no JS__. 32 | 33 | [__Live Website__][demo] 34 | -------------------------------------------------------------------------------- /playground/the-vue-point/composables.d.ts: -------------------------------------------------------------------------------- 1 | // generated by iles 2 | // We suggest you to commit this file into source control 3 | 4 | declare global { 5 | const definePageComponent: typeof import('iles')['definePageComponent'] 6 | const useDocuments: typeof import('iles')['useDocuments'] 7 | const useHead: typeof import('iles')['useHead'] 8 | const usePage: typeof import('iles')['usePage'] 9 | const useRoute: typeof import('iles')['useRoute'] 10 | } 11 | 12 | export { } 13 | -------------------------------------------------------------------------------- /playground/the-vue-point/iles.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'iles' 2 | 3 | import excerpt from '@islands/excerpt' 4 | import feed from '@islands/feed' 5 | import headings from '@islands/headings' 6 | import icons from '@islands/icons' 7 | import images, { hdPreset } from '@islands/images' 8 | import prism from '@islands/prism' 9 | import reactivityTransform from '@vue-macros/reactivity-transform/vite' 10 | 11 | import UnoCSS from 'unocss/vite' 12 | import inspect from 'vite-plugin-inspect' 13 | 14 | const presets = { 15 | narrow: hdPreset({ 16 | width: 200, 17 | widths: [200], 18 | formats: { 19 | avif: { quality: 44 }, 20 | webp: { quality: 44 }, 21 | original: {}, 22 | }, 23 | }), 24 | post: hdPreset({ 25 | widths: [440, 758], 26 | formats: { 27 | avif: { quality: 44 }, 28 | webp: { quality: 44 }, 29 | original: {}, 30 | }, 31 | }), 32 | } 33 | 34 | export default defineConfig({ 35 | siteUrl: 'https://the-vue-point-with-iles.netlify.app/', 36 | turbo: true, 37 | jsx: 'solid', 38 | prettyUrls: false, 39 | svelte: true, 40 | modules: [ 41 | excerpt({ separator: ['hr', 'h2', 'excerpt', 'Excerpt'] }), 42 | feed(), 43 | headings(), 44 | icons(), 45 | prism(), 46 | images(presets), 47 | ], 48 | // Example: Configure all posts to use a different layout without having to 49 | // add `layout: 'post'` in every file. 50 | extendFrontmatter (frontmatter, filename) { 51 | if (filename.includes('/posts/')) 52 | frontmatter.layout ||= 'post' 53 | }, 54 | markdown: { 55 | withImageSrc (src) { 56 | if (!src.includes('?')) 57 | return `${src}?preset=post` 58 | }, 59 | remarkPlugins: ['remark-gfm'], 60 | }, 61 | vite: { 62 | plugins: [ 63 | reactivityTransform(), 64 | UnoCSS() as any, 65 | Boolean(process.env.DEBUG) && inspect() as any, 66 | ], 67 | }, 68 | }) 69 | -------------------------------------------------------------------------------- /playground/the-vue-point/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /playground/the-vue-point/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-blog", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "private": true, 6 | "scripts": { 7 | "dev": "iles", 8 | "build": "iles build", 9 | "preview": "iles preview --open", 10 | "now": "npm run build && npm run preview", 11 | "check": "vue-tsc --noEmit", 12 | "lint": "eslint .", 13 | "lint:fix": "eslint . --fix" 14 | }, 15 | "engines": { 16 | "node": "^14.18 || >= 16.0.0" 17 | }, 18 | "devDependencies": { 19 | "@iconify-json/carbon": "^1.1.36", 20 | "@islands/excerpt": "workspace:^0.10.0-beta.1", 21 | "@islands/feed": "workspace:*", 22 | "@islands/headings": "workspace:^0.10.0-beta.1", 23 | "@islands/icons": "workspace:*", 24 | "@islands/images": "workspace:*", 25 | "@islands/prism": "workspace:*", 26 | "@vue-macros/reactivity-transform": "^1.0.4", 27 | "iles": "workspace:*", 28 | "remark-gfm": "^4.0.0", 29 | "unocss": "^0.61.8", 30 | "vue-tsc": "^2.1.10" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /playground/the-vue-point/public/_headers: -------------------------------------------------------------------------------- 1 | /assets/* 2 | cache-control: max-age=31536000 3 | cache-control: immutable 4 | 5 | /logo.svg 6 | cache-control: max-age=31536000 7 | cache-control: immutable 8 | 9 | /favicon.ico 10 | cache-control: max-age=31536000 11 | cache-control: immutable 12 | -------------------------------------------------------------------------------- /playground/the-vue-point/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElMassimo/iles/c9e95d729013a8f8f425e894cc7785b954cbcd85/playground/the-vue-point/public/favicon.ico -------------------------------------------------------------------------------- /playground/the-vue-point/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/app.ts: -------------------------------------------------------------------------------- 1 | import { defineApp } from 'iles' 2 | 3 | import 'virtual:uno.css' 4 | import '@unocss/reset/tailwind-compat.css' 5 | import '~/style.css' 6 | import 'prismjs/themes/prism-tomorrow.css' 7 | 8 | export default defineApp({ 9 | enhanceApp ({ app, head, router }) { 10 | // Configure the Vue app 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/components/Author.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 50 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/components/BackLink.tsx: -------------------------------------------------------------------------------- 1 | /** @jsxImportSource solid-js */ 2 | 3 | const BackLink = ({ href, children }: { href: string, children?: any }) => 4 | ← { children } 5 | 6 | export default BackLink 7 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/components/MetaTags.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/components/NavBarLinks.svelte: -------------------------------------------------------------------------------- 1 |
2 | Source 9 | · 10 | RSS 11 | · 12 | Vuejs.org → 19 |
20 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/components/PostDate.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 31 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/images/bench.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7e1858562aea765b1b409ef8fc13d6e0c2da099887f2709c8da738ee410939ed 3 | size 257001 4 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/images/one-piece.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3503d5cd26f0312e8d60ec654a66172a81da718ad85b44b8c8b0eabb991cb55f 3 | size 60745 4 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 37 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/logic/pagination.ts: -------------------------------------------------------------------------------- 1 | import type { StaticPath } from 'iles' 2 | 3 | export function paginate (items: T[], args: { pageSize?: number; pageParam?: string } = {}): StaticPath[] { 4 | const { pageSize = 10, pageParam = 'page' } = args 5 | const pagesCount = Math.max(1, Math.ceil(items.length / pageSize)) 6 | return Array.from({ length: pagesCount }, (_, i) => i + 1) 7 | .map((pageNumber) => { 8 | const firstItem = (pageNumber - 1) * pageSize 9 | return { 10 | params: { [pageParam]: String(pageNumber) }, 11 | props: { 12 | items: items.slice(firstItem, firstItem + pageSize), 13 | nextPage: pageNumber !== pagesCount ? pageNumber + 1 : undefined, 14 | prevPage: pageNumber === 1 ? undefined : pageNumber - 1, 15 | }, 16 | } 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/logic/posts.ts: -------------------------------------------------------------------------------- 1 | import type { Document, PageComponent } from 'iles' 2 | import { computed } from 'vue' 3 | 4 | export interface Post extends PageComponent { 5 | date: Date 6 | author: string 7 | title: string 8 | twitter: string 9 | } 10 | 11 | function byDate (a: Document, b: Document) { 12 | return Number(new Date(b.date)) - Number(new Date(a.date)) 13 | } 14 | 15 | export function getPosts () { 16 | const posts = useDocuments('~/pages/posts') 17 | return computed(() => posts.value.sort(byDate)) 18 | } 19 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/pages/404.vue: -------------------------------------------------------------------------------- 1 | 2 | title: Not Found 3 | hideFooter: true 4 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/pages/feed.vue: -------------------------------------------------------------------------------- 1 | 2 | path: /feed.rss 3 | 4 | 5 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | alias: ['/posts'] 3 | 4 | 5 | 10 | 11 | 52 | -------------------------------------------------------------------------------- /playground/the-vue-point/src/site.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: 'The Vue Point', 3 | description: 'Updates, tips & opinions from the maintainers of Vue.js.', 4 | } 5 | -------------------------------------------------------------------------------- /playground/the-vue-point/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "moduleResolution": "node", 7 | "jsx": "preserve", 8 | "jsxFactory": "h", 9 | "jsxFragmentFactory": "Fragment", 10 | "strict": true, 11 | "declaration": true, 12 | "noUnusedLocals": true, 13 | "skipLibCheck": true, 14 | "esModuleInterop": true, 15 | "lib": ["esnext", "DOM"], 16 | "types": [ 17 | "@vue-macros/reactivity-transform/macros-global" 18 | ], 19 | "paths": { 20 | "~/*": ["src/*"] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /playground/the-vue-point/uno.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineConfig, 3 | presetIcons, 4 | presetTypography, 5 | presetUno, 6 | transformerDirectives, 7 | } from 'unocss' 8 | import transformerVariantGroup from '@unocss/transformer-variant-group' 9 | 10 | export default defineConfig({ 11 | transformers: [transformerDirectives(), transformerVariantGroup()], 12 | presets: [ 13 | presetUno(), 14 | presetTypography({ 15 | cssExtend: { 16 | '.prose pre': { 17 | 'background-color': '#1f2937 !important', 18 | 'color': '#e5e7eb !important', 19 | }, 20 | }, 21 | }), 22 | presetIcons({ 23 | prefix: 'i-', // default prefix 24 | }), 25 | ], 26 | safelist: ['blockquote', 'figure', 'code', 'p', 'a'], 27 | }) 28 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/hydration/ 3 | - packages/prerender/ 4 | - packages/headings/ 5 | - packages/images/ 6 | - packages/mdx/ 7 | - packages/excerpt/ 8 | - packages/feed/ 9 | - packages/icons/ 10 | - packages/pages/ 11 | - packages/prism/ 12 | - packages/iles/ 13 | - packages/pwa/ 14 | - playground/the-vue-point/ 15 | - docs 16 | -------------------------------------------------------------------------------- /scripts/changelog.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import fs from 'node:fs' 3 | import minimist from 'minimist' 4 | import { execa } from 'execa' 5 | import { fileURLToPath } from 'url' 6 | 7 | const args = minimist(process.argv.slice(2)) 8 | const name = args._[0]?.trim() || 'iles' 9 | const __dirname = path.dirname(fileURLToPath(import.meta.url)) 10 | 11 | /** 12 | * @param {string} bin 13 | * @param {string[]} args 14 | * @param {object} opts 15 | */ 16 | const run = async (bin, args, opts = {}) => await execa(bin, args, { stdio: 'inherit', ...opts }) 17 | 18 | /** 19 | * @param {string} paths 20 | */ 21 | const resolve = paths => path.resolve(__dirname, `../packages/${name}/${paths}`) 22 | 23 | const tagPrefix = name === 'iles' ? 'v' : `${name}@` 24 | 25 | async function main () { 26 | await run('npx', [ 27 | 'conventional-changelog', 28 | '-p', 'angular', 29 | '-i', resolve('CHANGELOG.md'), 30 | '-s', 31 | '-t', tagPrefix, 32 | '--pkg', resolve('package.json'), 33 | '--commit-path', `./packages/${name}`, 34 | ]) 35 | } 36 | 37 | main().catch((err) => { 38 | console.error(err) 39 | }) 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "moduleResolution": "bundler", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "skipLibCheck": true, 9 | "noUnusedLocals": true, 10 | "resolveJsonModule": true, 11 | "verbatimModuleSyntax": true, 12 | "jsx": "preserve", 13 | "lib": ["esnext", "dom", "dom.iterable"] 14 | }, 15 | "exclude": [ 16 | "**/dist", 17 | "**/node_modules", 18 | "**/test" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------