UNPKG

wxt

Version:

⚡ Next-gen Web Extension Framework

202 lines (198 loc) 7.09 kB
import { normalizePath } from "./utils/paths.mjs"; import { getEntrypointBundlePath, isHtmlEntrypoint } from "./utils/entrypoints.mjs"; import "./utils/index.mjs"; import { getEntrypointGlobals, getGlobals } from "./utils/globals.mjs"; import { wxt } from "./wxt.mjs"; import { getPublicFiles, pathExists, writeFileIfDifferent } from "./utils/fs.mjs"; import { parseI18nMessages } from "./utils/i18n.mjs"; import { mkdir, readFile } from "node:fs/promises"; import path, { dirname, relative, resolve } from "node:path"; //#region src/core/generate-wxt-dir.ts /** * Generate and write all the files inside the `InternalConfig.typesDir` * directory. */ async function generateWxtDir(entrypoints) { await mkdir(wxt.config.typesDir, { recursive: true }); const entries = [{ module: "wxt/vite-builder-env" }]; wxt.config.userModules.forEach((module) => { if (module.type === "node_module") entries.push({ module: module.id }); }); entries.push(await getPathsDeclarationEntry(entrypoints)); entries.push(await getI18nDeclarationEntry()); entries.push(await getGlobalsDeclarationEntry()); entries.push(await getTsConfigEntry()); await wxt.hooks.callHook("prepare:types", wxt, entries); entries.push(getMainDeclarationEntry(entries)); const absoluteFileEntries = entries.filter((entry) => "path" in entry).map((entry) => ({ ...entry, path: resolve(wxt.config.wxtDir, entry.path) })); await Promise.all(absoluteFileEntries.map(async (file) => { await mkdir(dirname(file.path), { recursive: true }); await writeFileIfDifferent(file.path, file.text); })); } async function getPathsDeclarationEntry(entrypoints) { const paths = entrypoints.map((entry) => getEntrypointBundlePath(entry, wxt.config.outDir, getEntrypointPublicExt(entry))).concat(await getPublicFiles()); await wxt.hooks.callHook("prepare:publicPaths", wxt, paths); const unions = [ ` | ""`, ` | "/"`, ...paths.map(normalizePublicPathEntry).sort((a, b) => a.path.localeCompare(b.path) || a.type.localeCompare(b.type)).map(renderPublicPathUnionMember) ].join("\n"); return { path: "types/paths.d.ts", text: `// Generated by wxt import "wxt/browser"; declare module "wxt/browser" { export type PublicPath = {{ union }} type HtmlPublicPath = Extract<PublicPath, \`\${string}.html\`> export interface WxtRuntime { getURL(path: PublicPath): string; getURL(path: \`\${HtmlPublicPath}\${string}\`): string; } } `.replace("{{ union }}", unions || " | never"), tsReference: true }; } function normalizePublicPathEntry(entry) { if (typeof entry === "string") return { type: "string", path: normalizePath(entry) }; return { type: entry.type, path: normalizePath(entry.path) }; } function renderPublicPathUnionMember(entry) { const path = entry.path.startsWith("/") ? entry.path : `/${entry.path}`; switch (entry.type) { case "templateLiteral": return ` | \`${path}\``; case "string": return ` | "${path}"`; } } function getEntrypointPublicExt(entry) { if (isHtmlEntrypoint(entry)) return ".html"; switch (entry.type) { case "content-script-style": case "unlisted-style": return ".css"; default: return ".js"; } } async function getI18nDeclarationEntry() { const defaultLocale = wxt.config.manifest.default_locale; const template = `// Generated by wxt import "wxt/browser"; declare module "wxt/browser" { /** * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage */ interface GetMessageOptions { /** * See https://developer.chrome.com/docs/extensions/reference/i18n/#method-getMessage */ escapeLt?: boolean } export interface WxtI18n extends I18n.Static { {{ overrides }} } } `; const defaultLocalePath = path.resolve(wxt.config.publicDir, "_locales", defaultLocale ?? "", "messages.json"); let messages; if (await pathExists(defaultLocalePath)) messages = parseI18nMessages(JSON.parse(await readFile(defaultLocalePath, "utf-8"))); else messages = parseI18nMessages({}); const renderGetMessageOverload = (keyType, description, translation) => { const commentLines = []; if (description) commentLines.push(...description.split("\n")); if (translation) { if (commentLines.length > 0) commentLines.push(""); commentLines.push(`"${translation}"`); } return ` ${commentLines.length > 0 ? `/**\n${commentLines.map((line) => ` * ${line}`.trimEnd()).join("\n")}\n */\n ` : ""}getMessage( messageName: ${keyType}, substitutions?: string | string[], options?: GetMessageOptions, ): string;`; }; const overrides = [...messages.map((message) => renderGetMessageOverload(`"${message.name}"`, message.description, message.message)), renderGetMessageOverload(messages.map((message) => `"${message.name}"`).join(" | "))]; return { path: "types/i18n.d.ts", text: template.replace("{{ overrides }}", overrides.join("\n")), tsReference: true }; } async function getGlobalsDeclarationEntry() { return { path: "types/globals.d.ts", text: [ "// Generated by wxt", "interface ImportMetaEnv {", ...[...getGlobals(wxt.config), ...getEntrypointGlobals("")].map((global) => ` readonly ${global.name}: ${global.type};`), "}", "interface ImportMeta {", " readonly env: ImportMetaEnv", "}", "" ].join("\n"), tsReference: true }; } function getMainDeclarationEntry(references) { const lines = ["// Generated by wxt"]; references.forEach((ref) => { if ("module" in ref) return lines.push(`/// <reference types="${ref.module}" />`); else if (ref.tsReference) { const absolutePath = resolve(wxt.config.wxtDir, ref.path); const relativePath = relative(wxt.config.wxtDir, absolutePath); lines.push(`/// <reference path="./${normalizePath(relativePath)}" />`); } }); return { path: "wxt.d.ts", text: lines.join("\n") + "\n" }; } async function getTsConfigEntry() { const dir = wxt.config.wxtDir; const getTsconfigPath = (path) => { const res = normalizePath(relative(dir, path)); if (res.startsWith(".") || res.startsWith("/")) return res; return "./" + res; }; const tsconfig = { compilerOptions: { lib: [ "ESNext", "DOM", "DOM.Iterable" ], target: "ESNext", module: "Preserve", moduleDetection: "force", moduleResolution: "Bundler", allowImportingTsExtensions: true, verbatimModuleSyntax: true, noEmit: true, strict: true, skipLibCheck: true, noFallthroughCasesInSwitch: true, noUncheckedIndexedAccess: true, noImplicitOverride: true, paths: Object.fromEntries(Object.entries(wxt.config.alias).map(([alias, absolutePath]) => [alias, getTsconfigPath(absolutePath)]).flatMap(([alias, path]) => [[alias, [path]], [alias + "/*", [path + "/*"]]])) }, include: [`${getTsconfigPath(wxt.config.root)}/**/*`, "./wxt.d.ts"], exclude: [getTsconfigPath(wxt.config.root) + "/**/node_modules", getTsconfigPath(wxt.config.outBaseDir)] }; await wxt.hooks.callHook("prepare:tsconfig", wxt, { tsconfig }); return { path: "tsconfig.json", text: JSON.stringify(tsconfig, null, 2) }; } //#endregion export { generateWxtDir };