From 660945c792024eb72cfea216e570d4075eb544a1 Mon Sep 17 00:00:00 2001 From: Aaron Date: Sun, 8 Dec 2024 08:55:22 -0600 Subject: [PATCH] docs: Add blog and first blog post to wxt.dev (#1261) --- CONTRIBUTING.md | 10 +++ docs/.vitepress/components/BlogHome.vue | 70 +++++++++++++++++ docs/.vitepress/components/BlogLayout.vue | 76 +++++++++++++++++++ .../.vitepress/components/BlogPostPreview.vue | 72 ++++++++++++++++++ docs/.vitepress/composables/useBlogDate.ts | 15 ++++ docs/.vitepress/config.ts | 35 ++++++++- docs/.vitepress/loaders/blog.data.ts | 3 + docs/.vitepress/theme/index.ts | 11 ++- docs/blog.md | 9 +++ .../2024-10-19-real-world-messaging.md | 12 +++ docs/blog/2024-12-06-using-imports-module.md | 72 ++++++++++++++++++ package.json | 1 + pnpm-lock.yaml | 22 ++++++ pnpm-workspace.yaml | 1 + 14 files changed, 402 insertions(+), 7 deletions(-) create mode 100644 docs/.vitepress/components/BlogHome.vue create mode 100644 docs/.vitepress/components/BlogLayout.vue create mode 100644 docs/.vitepress/components/BlogPostPreview.vue create mode 100644 docs/.vitepress/composables/useBlogDate.ts create mode 100644 docs/.vitepress/loaders/blog.data.ts create mode 100644 docs/blog.md create mode 100644 docs/blog/.drafts/2024-10-19-real-world-messaging.md create mode 100644 docs/blog/2024-12-06-using-imports-module.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c625a7b9..a2db1d16 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -174,3 +174,13 @@ npm i https://pkg.pr.new/@wxt-dev/module-react@main # Install `@wxt-dev/storage` from a specific commit: npm i https://pkg.pr.new/@wxt-dev/module-react@426f907 ``` + +## Blog Posts + +Anyone is welcome to submit a blog post on https://wxt.dev/blog! + +> [!NOTE] +> Before starting on a blog post, please message Aaron on Discord or start a discussion on GitHub to get permission to write about a topic, but most topics are welcome: Major version updates, tutorials, etc. + +- **English only**: Blog posts should be written in English. Unfortunately, our maintainers doesn't have the bandwidth right now to translate our docs, let alone blog posts. Sorry 😓 +- **AI**: Please only use AI to translate or proof-read your blog post. Don't generate the whole thing... We don't want to publish that. diff --git a/docs/.vitepress/components/BlogHome.vue b/docs/.vitepress/components/BlogHome.vue new file mode 100644 index 00000000..35aadf90 --- /dev/null +++ b/docs/.vitepress/components/BlogHome.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/docs/.vitepress/components/BlogLayout.vue b/docs/.vitepress/components/BlogLayout.vue new file mode 100644 index 00000000..3fb56be7 --- /dev/null +++ b/docs/.vitepress/components/BlogLayout.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/docs/.vitepress/components/BlogPostPreview.vue b/docs/.vitepress/components/BlogPostPreview.vue new file mode 100644 index 00000000..40582218 --- /dev/null +++ b/docs/.vitepress/components/BlogPostPreview.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/docs/.vitepress/composables/useBlogDate.ts b/docs/.vitepress/composables/useBlogDate.ts new file mode 100644 index 00000000..d746e9bd --- /dev/null +++ b/docs/.vitepress/composables/useBlogDate.ts @@ -0,0 +1,15 @@ +import { computed, toValue, MaybeRefOrGetter } from 'vue'; + +const MONTH_FORMATTER = new Intl.DateTimeFormat( + globalThis?.navigator?.language, + { + month: 'long', + }, +); + +export default function (date: MaybeRefOrGetter) { + return computed(() => { + const d = new Date(toValue(date)); + return `${MONTH_FORMATTER.format(d)} ${d.getDate()}, ${d.getFullYear()}`; + }); +} diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index a70a5082..aae104fa 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -21,14 +21,19 @@ import { groupIconVitePlugin, localIconLoader, } from 'vitepress-plugin-group-icons'; +import { Feed } from 'feed'; +import { writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +const origin = 'https://wxt.dev'; const title = 'Next-gen Web Extension Framework'; const titleSuffix = ' – WXT'; const description = "WXT provides the best developer experience, making it quick, easy, and fun to develop web extensions. With built-in utilities for building, zipping, and publishing your extension, it's easy to get started."; const ogTitle = `${title}${titleSuffix}`; -const ogUrl = 'https://wxt.dev'; -const ogImage = 'https://wxt.dev/social-preview.png'; +const ogUrl = origin; +const ogImage = `${origin}/social-preview.png`; const otherPackages = { analytics: analyticsVersion, @@ -69,7 +74,30 @@ export default defineConfig({ }, lastUpdated: true, sitemap: { - hostname: 'https://wxt.dev', + hostname: origin, + }, + + async buildEnd(site) { + // Only construct the RSS document for production builds + const { default: blogDataLoader } = await import('./loaders/blog.data'); + const posts = await blogDataLoader.load(); + const feed = new Feed({ + copyright: 'MIT', + id: 'wxt', + title: 'WXT Blog', + link: `${origin}/blog`, + }); + posts.forEach((post) => { + feed.addItem({ + date: post.frontmatter.date, + link: new URL(post.url, origin).href, + title: post.frontmatter.title, + description: post.frontmatter.description, + }); + }); + console.log('rss.xml:'); + console.log(feed.rss2()); + await writeFile(join(site.outDir, 'rss.xml'), feed.rss2(), 'utf8'); }, head: [ @@ -126,6 +154,7 @@ export default defineConfig({ navItem('Guide', '/guide/installation'), navItem('Examples', '/examples'), navItem('API', '/api/reference/wxt'), + navItem('Blog', '/blog'), navItem(`v${wxtVersion}`, [ navItem('wxt', [ navItem(`v${wxtVersion}`, '/'), diff --git a/docs/.vitepress/loaders/blog.data.ts b/docs/.vitepress/loaders/blog.data.ts new file mode 100644 index 00000000..980fd820 --- /dev/null +++ b/docs/.vitepress/loaders/blog.data.ts @@ -0,0 +1,3 @@ +import { createContentLoader } from 'vitepress'; + +export default createContentLoader('blog/*.md'); diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index b5711598..e616c58a 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -3,15 +3,18 @@ import Icon from '../components/Icon.vue'; import EntrypointPatterns from '../components/EntrypointPatterns.vue'; import UsingWxtSection from '../components/UsingWxtSection.vue'; import ExampleSearch from '../components/ExampleSearch.vue'; +import BlogLayout from '../components/BlogLayout.vue'; import './custom.css'; import 'virtual:group-icons.css'; export default { extends: DefaultTheme, enhanceApp(ctx) { - ctx.app.component('Icon', Icon); - ctx.app.component('EntrypointPatterns', EntrypointPatterns); - ctx.app.component('UsingWxtSection', UsingWxtSection); - ctx.app.component('ExampleSearch', ExampleSearch); + ctx.app + .component('Icon', Icon) + .component('EntrypointPatterns', EntrypointPatterns) + .component('UsingWxtSection', UsingWxtSection) + .component('ExampleSearch', ExampleSearch) + .component('blog', BlogLayout); }, }; diff --git a/docs/blog.md b/docs/blog.md new file mode 100644 index 00000000..f49c18e6 --- /dev/null +++ b/docs/blog.md @@ -0,0 +1,9 @@ +--- +layout: page +--- + + + + diff --git a/docs/blog/.drafts/2024-10-19-real-world-messaging.md b/docs/blog/.drafts/2024-10-19-real-world-messaging.md new file mode 100644 index 00000000..f161280f --- /dev/null +++ b/docs/blog/.drafts/2024-10-19-real-world-messaging.md @@ -0,0 +1,12 @@ +--- +layout: blog +title: Real World Messaging +description: | + The extension messaging APIs are difficult to learn. Let's go beyond the simple examples from Chrome and Firefox's documentation to build our own simple messaging system from scratch. +authors: + - name: Aaron Klinker + github: aklinker1 +date: 2024-10-20T04:54:23.601Z +--- + +Test content **bold** _italic_ diff --git a/docs/blog/2024-12-06-using-imports-module.md b/docs/blog/2024-12-06-using-imports-module.md new file mode 100644 index 00000000..2ce59105 --- /dev/null +++ b/docs/blog/2024-12-06-using-imports-module.md @@ -0,0 +1,72 @@ +--- +layout: blog +title: Introducing #imports +description: Learn how WXT's new #imports module works and how to use it. +authors: + - name: Aaron Klinker + github: aklinker1 +date: 2024-12-06T14:39:00.000Z +--- + +WXT v0.20 introduced a new way of manually importing its APIs: **the `#imports` module**. This module was introduced to simplify import statements and provide more visibility into all the APIs WXT provides. + + +```ts +import { browser } from 'wxt/storage'; // [!code --] +import { createShadowRootUi } from 'wxt/utils/content-script-ui/shadow-root'; // [!code --] +import { defineContentScript } from 'wxt/utils/define-content-script'; // [!code --] +import { injectScript } from 'wxt/utils/inject-script'; // [!code --] +import { // [!code ++] + browser, createShadowRootUi, defineContentScript, injectScript // [!code ++] +} from '#imports'; // [!code ++] +``` + +The `#imports` module is considered a "virtual module", because the file doesn't actually exist. At build-time, imports are split into individual statements for each API: + +:::code-group + +```ts [What you write] +import { defineContentScript, injectScript } from '#imports'; +``` + +```ts [What the bundler sees] +import { defineContentScript } from 'wxt/utils/define-content-script'; +import { injectScript } from 'wxt/utils/inject-script'; +``` + +::: + +Think of `#imports` as a convenient way to access all of WXT's APIs from one place, without impacting performance or bundle size. + +This enables better tree-shaking compared to v0.19 and below. + +:::tip Need to lookup the full import path of an API? +Open up your project's `.wxt/types/imports-module.d.ts` file. +::: + +## Mocking + +When writing tests, you might need to mock APIs from the `#imports` module. While mocking these APIs is very easy, it may not be immediately clear how to accomplish it. + +Let's look at an example using Vitest. When [configured with `wxt/testing`](/guide/essentials/unit-testing#vitest), Vitest sees the same transformed code as the bundler. That means to mock an API from `#imports`, you need to call `vi.mock` with the real import path, not `#imports`: + +```ts +import { injectScript } from '#imports'; +import { vi } from 'vitest'; + +vi.mock('wxt/utils/inject-script') +const injectScriptMock = vi.mocked(injectScript); + +injectScriptMock.mockReturnValueOnce(...); +``` + +## Conclusion + +You don't have to use `#imports` if you don't like - you can continue importing APIs from their submodules. However, using `#imports` is the recommended approach moving forwards. + +- As more APIs are added, you won't have to memorize additional import paths. +- If breaking changes are made to import paths in future major versions, `#imports` won't break. + +Happy Coding 😄 + +> P.S. Yes, this is exactly how [Nuxt's `#imports`](https://nuxt.com/docs/guide/concepts/auto-imports#explicit-imports) works! We use the exact same library, [`unimport`](https://github.com/unjs/unimport). diff --git a/package.json b/package.json index 58e92881..ab221e04 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "changelogen": "catalog:", "consola": "catalog:", "fast-glob": "catalog:", + "feed": "catalog:", "fs-extra": "catalog:", "lint-staged": "catalog:", "markdown-it-footnote": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1637026d..9dcf32f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,6 +120,9 @@ catalogs: fast-glob: specifier: ^3.3.2 version: 3.3.2 + feed: + specifier: ^4.2.2 + version: 4.2.2 filesize: specifier: ^10.1.6 version: 10.1.6 @@ -328,6 +331,9 @@ importers: fast-glob: specifier: 'catalog:' version: 3.3.2 + feed: + specifier: 'catalog:' + version: 4.2.2 fs-extra: specifier: 'catalog:' version: 11.2.0 @@ -3304,6 +3310,10 @@ packages: picomatch: optional: true + feed@4.2.2: + resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} + engines: {node: '>=0.4.0'} + filesize@10.1.6: resolution: {integrity: sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==} engines: {node: '>= 10.4.0'} @@ -5417,6 +5427,10 @@ packages: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} + xml-js@1.6.11: + resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} + hasBin: true + xml2js@0.5.0: resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} engines: {node: '>=4.0.0'} @@ -7865,6 +7879,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + feed@4.2.2: + dependencies: + xml-js: 1.6.11 + filesize@10.1.6: {} fill-range@7.1.1: @@ -10128,6 +10146,10 @@ snapshots: xdg-basedir@5.1.0: {} + xml-js@1.6.11: + dependencies: + sax: 1.2.4 + xml2js@0.5.0: dependencies: sax: 1.2.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 43c62341..c2760417 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -48,6 +48,7 @@ catalog: esbuild: ^0.25.0 extract-zip: ^2.0.1 fast-glob: ^3.3.2 + feed: ^4.2.2 filesize: ^10.1.6 fs-extra: ^11.2.0 get-port-please: ^3.1.2