UNPKG

unplugin-solid

Version:

Transform Solid.js code in various bundlers powered by unplugin

202 lines (199 loc) 7.18 kB
import { createRequire } from "node:module"; import { readFileSync } from "node:fs"; import { transformAsync } from "@babel/core"; import { createFilter } from "@rollup/pluginutils"; import solid from "babel-preset-solid"; import { mergeAndConcat } from "merge-anything"; import solidRefresh from "solid-refresh/babel"; import { createUnplugin } from "unplugin"; import { version } from "vite"; import { crawlFrameworkPkgs } from "vitefu"; //#region src/core/utils.ts const require = createRequire(import.meta.url); function getExtension(filename) { const index = filename.lastIndexOf("."); return index === -1 ? "" : filename.slice(Math.max(0, index)).replace(/\?.+$/, ""); } function containsSolidField(fields) { const keys = Object.keys(fields); for (const key of keys) { if (key === "solid") return true; if (typeof fields[key] === "object" && fields[key] != null && containsSolidField(fields[key])) return true; } return false; } const getJestDomExport = (setupFiles) => setupFiles?.some((path) => /jest-dom/.test(path)) ? void 0 : ["@testing-library/jest-dom/vitest", "@testing-library/jest-dom/extend-expect"].find((path) => { try { require.resolve(path); return true; } catch { return false; } }); /** * This basically normalize all aliases of the config into * the array format of the alias. * * eg: alias: { '@': 'src/' } => [{ find: '@', replacement: 'src/' }] */ const normalizeAliases = (alias = []) => Array.isArray(alias) ? alias : Object.entries(alias).map(([find, replacement]) => ({ find, replacement })); //#endregion //#region src/core/index.ts const runtimePublicPath = "/@solid-refresh"; const runtimeFilePath = require.resolve("solid-refresh/dist/solid-refresh.mjs"); const runtimeCode = readFileSync(runtimeFilePath, "utf-8"); const SOLID_EXTERNALS = [ "solid-js", "solid-js/web", "solid-js/store", "solid-js/html", "solid-js/h" ]; const plugin = createUnplugin((options = {}, meta) => { const filter = createFilter(options.include, options.exclude); let needHmr = false; let replaceDev = false; let projectRoot = process.cwd(); let isTestMode = false; return { name: "unplugin-solid", enforce: "pre", vite: { async config(userConfig, { command }) { replaceDev = options.dev === true || options.dev !== false && command === "serve"; projectRoot = userConfig.root; isTestMode = userConfig.mode === "test"; userConfig.resolve ??= {}; userConfig.resolve.alias = normalizeAliases(userConfig.resolve && userConfig.resolve.alias); const solidPkgsConfig = await crawlFrameworkPkgs({ viteUserConfig: userConfig, root: projectRoot ?? process.cwd(), isBuild: command === "build", isFrameworkPkgByJson(pkgJson) { return containsSolidField(pkgJson.exports ?? {}); } }); const nestedDeps = replaceDev ? SOLID_EXTERNALS : []; const userTest = userConfig.test ?? {}; const test = {}; if (userConfig.mode === "test") { const userSetupFiles = typeof userTest.setupFiles === "string" ? [userTest.setupFiles] : userTest.setupFiles ?? []; if (!userTest.environment && !options.ssr) test.environment = "jsdom"; if (!userTest.server?.deps?.external?.find((item) => /solid-js/.test(item.toString()))) test.server = { deps: { external: [/solid-js/] } }; if (!userTest.browser?.enabled) { const jestDomImport = getJestDomExport(userSetupFiles); if (jestDomImport) test.setupFiles = [jestDomImport]; } } const isViteGreaterThan6 = +version.split(".")[0] >= 6; return { resolve: { conditions: isViteGreaterThan6 ? void 0 : [ "solid", ...replaceDev ? ["development"] : [], ...userConfig.mode === "test" && !options.ssr ? ["browser"] : [] ], dedupe: nestedDeps, alias: [{ find: /^solid-refresh$/, replacement: runtimePublicPath }] }, optimizeDeps: { include: [...nestedDeps, ...solidPkgsConfig.optimizeDeps.include], exclude: solidPkgsConfig.optimizeDeps.exclude }, ssr: solidPkgsConfig.ssr, ...test.server ? { test } : {} }; }, async configEnvironment(name, config, opts) { config.resolve ??= {}; if (config.resolve.conditions == null) { const { defaultClientConditions, defaultServerConditions } = await import("vite"); config.resolve.conditions = config.consumer === "client" || name === "client" || opts.isSsrTargetWebworker ? [...defaultClientConditions] : [...defaultServerConditions]; } config.resolve.conditions = [ "solid", ...replaceDev ? ["development"] : [], ...isTestMode && !opts.isSsrTargetWebworker ? ["browser"] : [], ...config.resolve.conditions ]; }, configResolved(config) { needHmr = config.command === "serve" && config.mode !== "production" && options.hot !== false; }, resolveId(id) { if (id === runtimePublicPath) return id; }, load(id) { if (id === runtimePublicPath) return runtimeCode; } }, rolldown: { options(opts) { opts.external ??= SOLID_EXTERNALS; opts.transform ??= { jsx: "preserve" }; } }, async transform(source, id) { const isSsr = !!options.ssr; const currentFileExtension = getExtension(id); const extensionsToWatch = options.extensions ?? []; const allExtensions = extensionsToWatch.map((extension) => typeof extension === "string" ? extension : extension[0]); if (!filter(id)) return null; id = id.replace(/\?.*$/, ""); if (!(/\.[mc]?[tj]sx$/i.test(id) || allExtensions.includes(currentFileExtension))) return null; const inNodeModules = /node_modules/.test(id); let solidOptions; if (options.ssr) solidOptions = isSsr ? { generate: "ssr", hydratable: true } : { generate: "dom", hydratable: true }; else solidOptions = { generate: "dom", hydratable: false }; const shouldBeProcessedWithTypescript = /\.[mc]?tsx$/i.test(id) || extensionsToWatch.some((extension) => { if (typeof extension === "string") return extension.includes("tsx"); const [extensionName, extensionOptions] = extension; if (extensionName !== currentFileExtension) return false; return extensionOptions.typescript; }); const plugins = ["jsx"]; if (shouldBeProcessedWithTypescript) plugins.push("typescript"); const opts = { root: projectRoot, filename: id, sourceFileName: id, presets: [[solid, { ...solidOptions, ...options.solid ?? {} }]], plugins: needHmr && !isSsr && !inNodeModules ? [[solidRefresh, { bundler: meta.framework }]] : [], ast: false, sourceMaps: true, configFile: false, babelrc: false, parserOpts: { plugins } }; let babelUserOptions = {}; if (options.babel) if (typeof options.babel === "function") { const babelOptions$1 = options.babel(source, id, isSsr); babelUserOptions = babelOptions$1 instanceof Promise ? await babelOptions$1 : babelOptions$1; } else babelUserOptions = options.babel; const babelOptions = mergeAndConcat(babelUserOptions, opts); const { code, map } = await transformAsync(source, babelOptions); return { code, map }; } }; }); //#endregion export { plugin };