diff --git a/.gitignore b/.gitignore index 237e6fb5..4fbca98d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,11 @@ dist node_modules temp .eslintcache + +# docs +docs/showcase/*.md +!docs/showcase/index.md +docs/.vitepress/cache +docs/.vitepress/components.d.ts +docs/.env +docs/.vitepress/data/repository.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..79694963 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +ignore-workspace-root-check=true +shell-emulator=true diff --git a/docs/.env.example b/docs/.env.example new file mode 100644 index 00000000..0c6a33dd --- /dev/null +++ b/docs/.env.example @@ -0,0 +1 @@ +GITHUB_TOKEN= \ No newline at end of file diff --git a/docs/.vitepress/components/RepoInfo.vue b/docs/.vitepress/components/RepoInfo.vue new file mode 100644 index 00000000..f1bed744 --- /dev/null +++ b/docs/.vitepress/components/RepoInfo.vue @@ -0,0 +1,40 @@ + + + diff --git a/docs/.vitepress/components/Repositories.vue b/docs/.vitepress/components/Repositories.vue new file mode 100644 index 00000000..37900e43 --- /dev/null +++ b/docs/.vitepress/components/Repositories.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 00000000..cabe813a --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,88 @@ +import MarkdownItGitHubAlerts from 'markdown-it-github-alerts' +import { defineConfig } from 'vitepress' + +import { transformerTwoslash } from 'vitepress-plugin-twoslash' +import { repositoryMeta } from './data/meta' +import { description, ogImage, title } from './constance' + +import vite from './vite.config' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title, + description, + lastUpdated: true, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Guide', link: '/guide/', activeMatch: '/guide/' }, + { text: 'Showcase', link: '/showcase/', activeMatch: '/showcase/' }, + ], + search: { + provider: 'local', + }, + logo: { + light: '/logo_light.svg', + dark: '/logo_dark.svg', + }, + + sidebar: { + '/': [ + { + text: 'Guide', + items: [ + { text: 'Getting Started', link: '/guide/' }, + // { text: 'Why Unplugin', link: '/guide/why' }, + { text: 'Plugin Conventions', link: '/guide/plugin-conventions' }, + ], + }, + { + text: 'Showcase', + link: '/showcase/', + items: [ + { + text: 'Overview', + link: '/showcase/', + }, + ...repositoryMeta.map(repo => ( + { + text: repo.name, + link: `/showcase/${repo.name}`, + } + )), + ], + }, + ], + }, + + socialLinks: [ + { icon: 'github', link: 'https://github.com/unplugin' }, + ], + + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright (c) 2021-PRESENT UnJS Team', + }, + }, + head: [ + ['meta', { name: 'theme-color', content: '#ffffff' }], + ['link', { rel: 'icon', href: '/logo.svg', type: 'image/svg+xml' }], + ['meta', { name: 'author', content: 'Nuxt Contrib' }], + ['meta', { property: 'og:title', content: title }], + ['meta', { property: 'og:image', content: ogImage }], + ['meta', { property: 'og:description', content: description }], + ['meta', { name: 'twitter:card', content: 'summary_large_image' }], + ['meta', { name: 'twitter:image', content: ogImage }], + ['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1.0, viewport-fit=cover' }], + ], + markdown: { + config: (md: any) => { + md.use(MarkdownItGitHubAlerts) + }, + codeTransformers: [ + transformerTwoslash(), + ], + }, + ignoreDeadLinks: true, + vite: vite as any, +}) diff --git a/docs/.vitepress/constance.ts b/docs/.vitepress/constance.ts new file mode 100644 index 00000000..f00d9623 --- /dev/null +++ b/docs/.vitepress/constance.ts @@ -0,0 +1,4 @@ +export const title = 'Unplugin' +export const description = 'Unified plugin system. Support Vite, Rollup, webpack, esbuild, and every frameworks on top of them.' +export const url = 'https://unplugin.vercel.app/' +export const ogImage = `${url}/og.png` diff --git a/docs/.vitepress/data/gen-files.ts b/docs/.vitepress/data/gen-files.ts new file mode 100644 index 00000000..8d710f03 --- /dev/null +++ b/docs/.vitepress/data/gen-files.ts @@ -0,0 +1,135 @@ +import 'dotenv/config' +import { writeFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { env } from 'node:process' +import { $fetch } from 'ofetch' +import { consola } from 'consola' +import type { Repository } from './repository.data' +import { repositoryMeta } from './meta' + +const GITHUB_TOKEN = env.GITHUB_TOKEN + +const gql = `#graphql +query repositoryQuery($owner: String!, $name: String!, $readme: String!) { + repository(owner: $owner, name: $name) { + name + stargazers { + totalCount + } + owner { + avatarUrl + login + } + description + primaryLanguage { + name + color + } + forkCount + object(expression: $readme) { + ... on Blob { + text + } + } + } +}` + +async function fetchRepo(meta: { + owner: string + name: string + readme?: string +}) { + const { owner, name, readme } = meta + + const _readme = readme || 'main:README.md' + try { + const results = await $fetch('https://api.github.com/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${GITHUB_TOKEN}`, + }, + body: JSON.stringify({ + query: gql, + variables: { + owner, + name, + readme: _readme, + }, + }), + }) + + const repositoryInfo = results.data.repository as Repository + + const markdownFrontmatter = `--- +title: ${repositoryInfo.name} +owner: ${repositoryInfo.owner.login} +name: ${repositoryInfo.name} +stars: ${repositoryInfo.stargazers.totalCount} +forks: ${repositoryInfo.forkCount} +outline: deep +--- + + + +--- + +` + + writeFileSync( + join(dirname(fileURLToPath(import.meta.url)), `../../showcase/${name}.md`), + markdownFrontmatter + repositoryInfo.object.text, + ) + consola.success(`[${name}.md]: generate success`) + return repositoryInfo + } + catch (error) { + consola.error(`[${name}.md]: generate failed: ${error}`) + } +} + +function main() { + if (!GITHUB_TOKEN) { + consola.error('GITHUB_TOKEN is missing, please refer to https://github.com/unplugin/docs#development') + return false + } + + const fetchs = repositoryMeta.map((repository) => { + return fetchRepo({ + name: repository.name, + owner: repository.owner, + readme: repository.defaultBranch ? `${repository.defaultBranch}:README.md` : 'main:README.md', + }) + }) + + Promise.allSettled(fetchs).then((res) => { + const repoMeta = res?.map((item) => { + if (item.status === 'fulfilled') { + return { + name: item.value?.name, + stargazers: item.value?.stargazers, + owner: item.value?.owner, + description: item.value?.description, + url: item.value?.url, + isTemplate: item.value?.isTemplate, + primaryLanguage: item.value?.primaryLanguage, + forkCount: item.value?.forkCount, + } + } + + return null + })?.filter(item => item && item.name) + + writeFileSync( + join(dirname(fileURLToPath(import.meta.url)), './repository.json'), + JSON.stringify(repoMeta, null, 2), + ) + consola.success('[repository.json] generate success!') + consola.success('All files generate done!') + }).catch((error) => { + consola.error(error) + }) +} + +main() diff --git a/docs/.vitepress/data/meta.ts b/docs/.vitepress/data/meta.ts new file mode 100644 index 00000000..f89f35b0 --- /dev/null +++ b/docs/.vitepress/data/meta.ts @@ -0,0 +1,52 @@ +export const repositoryMeta = [ + { + owner: 'unplugin', + name: 'unplugin-vue-components', + }, + { + owner: 'unplugin', + name: 'unplugin-icons', + }, + { + owner: 'unplugin', + name: 'unplugin-auto-import', + }, + { + owner: 'unplugin', + name: 'unplugin-vue2-script-setup', + }, + { + owner: 'unplugin', + name: 'unplugin-vue-markdown', + }, + { + owner: 'unplugin', + name: 'unplugin-swc', + }, + { + owner: 'unplugin', + name: 'unplugin-turbo-console', + }, + { + owner: 'unplugin', + name: 'unplugin-imagemin', + }, + { + owner: 'unplugin', + name: 'unplugin-vue-cssvars', + defaultBranch: 'master', + }, + { + owner: 'unplugin', + name: 'unplugin-vue', + }, + { + owner: 'unplugin', + name: 'unplugin-macros', + }, + { + owner: 'unplugin', + name: 'unplugin-vue-ce', + defaultBranch: 'master', + }, +] diff --git a/docs/.vitepress/data/repository.data.ts b/docs/.vitepress/data/repository.data.ts new file mode 100644 index 00000000..fec8aeeb --- /dev/null +++ b/docs/.vitepress/data/repository.data.ts @@ -0,0 +1,36 @@ +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +export interface Repository { + name: string + stargazers: { + totalCount: number + } + owner: { + avatarUrl: string + login: string + } + description: string + url: string + isTemplate: boolean + primaryLanguage: { + name: string + color: string + } + forkCount: number + object: { + text: string + } +} + +declare const data: Repository[] +export { data } + +export default { + watch: ['./repository.json'], + load() { + const fileContent = readFileSync(join(dirname(fileURLToPath(import.meta.url)), './repository.json'), 'utf-8') + return JSON.parse(fileContent) + }, +} diff --git a/docs/.vitepress/plugins/markdownTransform.ts b/docs/.vitepress/plugins/markdownTransform.ts new file mode 100644 index 00000000..2439873a --- /dev/null +++ b/docs/.vitepress/plugins/markdownTransform.ts @@ -0,0 +1,92 @@ +import { basename } from 'node:path' +import type { PluginOption } from 'vite' +import { repositoryMeta } from '../data/meta' + +const repos = repositoryMeta.map(({ name }) => `${name}`) + +export function MarkdownTransform(): PluginOption { + const MARKDOWN_LINK_RE = /(?\[.*?]\((?.*?)\)|.*?)".*?>)/g + const GH_RAW_URL = 'https://raw.githubusercontent.com' + const GH_URL = 'https://github.com/unplugin' + const images = ['png', 'jpg', 'jpeg', 'gif', 'svg'].map(ext => `.${ext}`) + return { + name: 'unplugin-md-transform', + enforce: 'pre', + async transform(code, id) { + // only transform markdown on meta files + if (!repos.includes(basename(id, '.md'))) + return null + + // https://github.com/unplugin/unplugin-vue-components/blob/main/README.md?plain=1#L66 + // Manual add line break + code = code.replaceAll('
', '
\n') + + // https://github.com/unplugin/unplugin-icons/blob/main/README.md?plain=1#L425 + code = code.replaceAll(' < ', ' < ').replaceAll(' > ', ' > ') + + // replace markdown img link + // code reference: https://github.com/unjs/ungh/blob/main/utils/markdown.ts + const { name, owner, defaultBranch } = repositoryMeta.find(({ name }) => name === basename(id, '.md'))! + const _defaultBranch = defaultBranch || 'main' + code = code.replaceAll(MARKDOWN_LINK_RE, (match, _, url: string | undefined, url2: string) => { + const path = url || url2 + // If path is already a URL, return the match + if (path.startsWith('http') || path.startsWith('https')) + return match + + // handle images and links differently + return match.includes(' match.includes(ext)) + ? match.replace(path, `${GH_RAW_URL}/${owner}/${name}/${_defaultBranch}/${path.replace(/^\.\//, '')}`) + : match.replace(path, `${GH_URL}/${name}/tree/${_defaultBranch}/${path.replace(/^\.\//, '')}`) + }) + + let useCode = code + let idx = 0 + + while (true) { + const detailIdx = useCode.indexOf('
', idx) + if (detailIdx === -1) + break + + const summaryIdx = useCode.indexOf('', idx + 10) + if (summaryIdx === -1) + break + + const endSummaryIdx = useCode.indexOf('', summaryIdx + 10) + if (endSummaryIdx === -1) + break + + const title = useCode.slice(summaryIdx + 9, endSummaryIdx) + .trim() + .replaceAll('
', '') + .replaceAll('
', '') + .replaceAll('
', '') + + const endDetailIdx = useCode.indexOf('
', endSummaryIdx + 11) + if (endDetailIdx === -1) + break + + const detailBody = useCode.slice(endSummaryIdx + 10, endDetailIdx) + .trim() + .replaceAll('
', '') + .replaceAll('
', '') + .replaceAll('
', '') + + let rest = useCode.slice(endDetailIdx + 11).trim() + // additional
in some readme packages between details + if (rest.startsWith('
')) + rest = rest.slice(4) + if (rest.startsWith('
')) + rest = rest.slice(5) + if (rest.startsWith('
')) + rest = rest.slice(6) + + useCode = `${useCode.slice(0, detailIdx)}\n::: details ${title}\n\n${detailBody}\n:::\n` + idx = useCode.length + useCode += rest + } + + return useCode + }, + } +} diff --git a/docs/.vitepress/theme/CustomLayout.vue b/docs/.vitepress/theme/CustomLayout.vue new file mode 100644 index 00000000..5b6f816f --- /dev/null +++ b/docs/.vitepress/theme/CustomLayout.vue @@ -0,0 +1,46 @@ + + + diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 00000000..6a95f6e2 --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,20 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from 'vue' +import type { EnhanceAppContext } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +import TwoSlashFloatingVue from 'vitepress-plugin-twoslash/client' +import CustomLayout from './CustomLayout.vue' + +import 'vitepress-plugin-twoslash/style.css' +import 'uno.css' +import './style.css' + +export default { + extends: DefaultTheme, + Layout: () => { + return h(CustomLayout) + }, + enhanceApp({ app }: EnhanceAppContext) { + app.use(TwoSlashFloatingVue as any) + }, +} diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 00000000..ff8bdd9c --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,130 @@ +@import 'markdown-it-github-alerts/styles/github-colors-light.css'; +@import 'markdown-it-github-alerts/styles/github-colors-dark-media.css'; +@import 'markdown-it-github-alerts/styles/github-base.css'; + +:root { + --vp-c-brand-1: #000; + --vp-c-brand-2: #383838; + --vp-c-brand-3: #09090b; + --vp-button-brand-bg: #171717; + --vp-c-text-2: #444; + } + + .dark { + --vp-c-brand-1: #fff; + --vp-c-brand-2: #71717a; + --vp-c-brand-3: #52525b; + --vp-c-brand-soft: #09090b50; + --vp-c-text-2: #999; + --vp-button-brand-bg: #ededed; + --vp-button-brand-text: #0a0a0a; + --vp-button-brand-hover-bg:#dad9d9; + --vp-button-brand-hover-text: #383838; + --vp-button-brand-active-text: #09090b; + --vp-home-hero-name-background: -webkit-linear-gradient( + 0deg, + #fff 30%, + #d1d5db 0% + )!important; + } + + :root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-hover-border: transparent; + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 0deg, + #333 30%, + #888 1% + ); +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} + +a > img { + display: inline; +} + +img[src*="features"] { + height: auto; + width: 48px; +} + +img[src*="/features/rspack"] { + margin-bottom: 28px!important; +} + +details > summary:hover { + cursor: pointer; + user-select: none; +} + +::view-transition-old(root), +::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} +::view-transition-old(root) { + z-index: 1; +} +::view-transition-new(root) { + z-index: 9999; +} +.dark::view-transition-old(root) { + z-index: 9999; +} +.dark::view-transition-new(root) { + z-index: 1; +} diff --git a/docs/.vitepress/uno.config.ts b/docs/.vitepress/uno.config.ts new file mode 100644 index 00000000..a25be60f --- /dev/null +++ b/docs/.vitepress/uno.config.ts @@ -0,0 +1,14 @@ +import { defineConfig, presetAttributify, presetIcons, presetUno, transformerDirectives } from 'unocss' + +export default defineConfig({ + presets: [ + presetUno(), + presetAttributify(), + presetIcons({ + scale: 1.2, + }), + ], + transformers: [ + transformerDirectives(), + ], +}) diff --git a/docs/.vitepress/vite.config.ts b/docs/.vitepress/vite.config.ts new file mode 100644 index 00000000..b4b08724 --- /dev/null +++ b/docs/.vitepress/vite.config.ts @@ -0,0 +1,23 @@ +import { fileURLToPath } from 'node:url' +import { defineConfig } from 'vite' +import Components from 'unplugin-vue-components/vite' +import Unocss from 'unocss/vite' +import Icons from 'unplugin-icons/vite' +import { MarkdownTransform } from './plugins/markdownTransform' + +export default defineConfig({ + plugins: [ + MarkdownTransform(), + Components({ + include: [/\.vue/, /\.md/], + dirs: '.vitepress/components', + dts: '.vitepress/components.d.ts', + }), + Unocss(fileURLToPath(new URL('./uno.config.ts', import.meta.url))), + Icons(), + ], + // https://github.com/antfu/shikiji/issues/86 + ssr: { + noExternal: ['shikiji-twoslash', 'vitepress-plugin-twoslash'], + }, +}) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..00171e20 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,37 @@ +

+ +

+ +

+Unplugin +

+

+Unified plugin system, Support Vite, Rollup, webpack, esbuild, and more +

+ +

+Documentation +

+ +## Development + +This project use [GitHub GraphQL API](https://docs.github.com/en/graphql) to generate the showcase data. So you need to create a [GitHub Personal Access Token](https://github.com/settings/personal-access-tokens/new) first. + +```bash +cp .env.example .env +``` + +```ini +# .env +GITHUB_TOKEN= +``` + +### Generate files + +```bash +pnpm gen-files +``` + +## Contributing + +Please refer to https://github.com/antfu/contribute diff --git a/docs/api-examples.md b/docs/api-examples.md new file mode 100644 index 00000000..6bd8bb5c --- /dev/null +++ b/docs/api-examples.md @@ -0,0 +1,49 @@ +--- +outline: deep +--- + +# Runtime API Examples + +This page demonstrates usage of some of the runtime APIs provided by VitePress. + +The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: + +```md + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+``` + + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+ +## More + +Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). diff --git a/docs/guide/index.md b/docs/guide/index.md new file mode 100644 index 00000000..62b0582f --- /dev/null +++ b/docs/guide/index.md @@ -0,0 +1,349 @@ +--- +outline: deep +lastUpdated: false +--- + +# Getting Started + +## Overview + +**Unplugin** is a library that offers an unified plugin system for various build tools. It extends the excellent [Rollup plugin API](https://rollupjs.org/plugin-development/#plugins-overview) to serve as the standard plugin interface, and provides a compatibility layer based on the build tools employed. + +**Unplugin** current supports: + +- [Vite](https://vitejs.dev/) +- [Rollup](https://rollupjs.org/) +- [webpack](https://webpack.js.org/) +- [esbuild](https://esbuild.github.io/) +- [Rspack](https://www.rspack.dev/) (⚠️ experimental) +- [Rolldown](https://rolldown.rs/) (⚠️ experimental) + +## Trying It Online + +You can try **Unplugin** in your browser directly. + +[![open](/open_in_codeflow.svg)](https://stackblitz.com/~/github.com/yuyinws/unplugin-starter?file=src/index.ts) + +## Creating an Unplugin package + +```shell +npx degit unplugin/unplugin-starter unplugin-starter +``` +> Check the [unplugin-starter](https://github.com/unplugin/unplugin-starter) repository for more details. + +## Plugin Installation + +### Pre-requisites + +- Node.js 14.0.0 or later + +::: warning +We will discontinue support for Node.js v14 & v16 in the next major release. +Please consider upgrading to Node.js v18 or higher. +::: + +### Install package + +::: code-group + +```bash [npm] +npm install unplugin-starter --save-dev +``` + +```bash [yarn] +yarn add unplugin-starter -D +``` + +```bash [pnpm] +pnpm add unplugin-starter -D +``` + +```bash [bun] +bun add unplugin-starter -D +``` +::: + +### Bundler & Framework Integration + +::: code-group + +```ts [Vite] +// vite.config.ts +import Starter from 'unplugin-starter/vite' + +export default defineConfig({ + plugins: [ + Starter({ /* options */ }), + ], +}) +``` + +```js [Rollup] +// rollup.config.js +import Starter from 'unplugin-starter/rollup' + +export default { + plugins: [ + Starter({ /* options */ }), + ], +} +``` + +```js [webpack] +// webpack.config.js +module.exports = { + /* ... */ + plugins: [ + require('unplugin-starter/webpack')({ /* options */ }) + ] +} +``` + +```js [Rspack] +// rspack.config.js +module.exports = { + /* ... */ + plugins: [ + require('unplugin-starter/rspack')({ /* options */ }) + ] +} +``` + +```js [esbuild] +// esbuild.config.js +import { build } from 'esbuild' +import Starter from 'unplugin-starter/esbuild' + +build({ + plugins: [Starter()], +}) +``` + +```js [Vue-CLI] +// vue.config.js +module.exports = { + configureWebpack: { + plugins: [ + require('unplugin-starter/webpack')({ /* options */ }), + ], + }, +} +``` + +```js [Nuxt] +// nuxt.config.ts +export default defineNuxtConfig({ + modules: [ + ['unplugin-starter/nuxt', { /* options */ }], + ], +}) +``` + +```js [Astro] +// astro.config.mjs +import { defineConfig } from 'astro/config' +import Starter from 'unplugin-turbo-console/astro' + +// https://astro.build/config +export default defineConfig({ + integrations: [ + Starter() + ] +}) +``` + +## Supported Hooks + +| Hook | Rollup | Vite | webpack 4 | webpack 5 | esbuild | Rspack | +| ----------------------------------------------------------------------------------| :-------------: | :--: | :-------: | :-------: | :-----------: | :----: | +| [`enforce`](https://vitejs.dev/guide/api-plugin.html#plugin-ordering) | ❌ 1 | ✅ | ✅ | ✅ | ❌ 1 | ✅ | +| [`buildStart`](https://rollupjs.org/plugin-development/#buildstart) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [`resolveId`](https://rollupjs.org/plugin-development/#resolveid) | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | +| `loadInclude`2 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [`load`](https://rollupjs.org/plugin-development/#load) | ✅ | ✅ | ✅ | ✅ | ✅ 3 | ✅ | +| `transformInclude`2 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [`transform`](https://rollupjs.org/plugin-development/#transform) | ✅ | ✅ | ✅ | ✅ | ✅ 3 | ✅ | +| [`watchChange`](https://rollupjs.org/plugin-development/#watchchange) | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| [`buildEnd`](https://rollupjs.org/plugin-development/#buildend) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [`writeBundle`](https://rollupjs.org/plugin-development/#writebundle)4 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | + +::: details Notice +1. Rollup and esbuild do not support using `enforce` to control the order of plugins. Users need to maintain the order manually. +2. webpack's id filter is outside of loader logic; an additional hook is needed for better perf on webpack. In Rollup and Vite, this hook has been polyfilled to match the behaviors. See for the following usage examples. +3. Although esbuild can handle both JavaScript and CSS and many other file formats, you can only return JavaScript in `load` and `transform` results. +4. Currently, `writeBundle` is only serves as a hook for the timing. It doesn't pass any arguments. +::: + +### Usage + +```ts{12-14,16-18} twoslash +import type { UnpluginFactory } from 'unplugin' +import { createUnplugin } from 'unplugin' + +export interface Options { + // define your plugin options here +} + +export const unpluginFactory: UnpluginFactory = options => ({ + name: 'unplugin-starter', + // webpack's id filter is outside of loader logic, + // an additional hook is needed for better perf on webpack + transformInclude(id) { + return id.endsWith('main.ts') + }, + // just like rollup transform + transform(code) { + return code.replace(/