diff --git a/.changeset/slow-lamps-help.md b/.changeset/slow-lamps-help.md new file mode 100644 index 00000000..a4799b30 --- /dev/null +++ b/.changeset/slow-lamps-help.md @@ -0,0 +1,5 @@ +--- +'@prefresh/vite': major +--- + +Add true vite 8 support with native rolldown rather than babel diff --git a/packages/vite/README.md b/packages/vite/README.md index f8852527..22358e0b 100644 --- a/packages/vite/README.md +++ b/packages/vite/README.md @@ -20,11 +20,17 @@ export default { }; ``` +`@prefresh/vite` configures Vite's Oxc JSX transform for Preact and composes +`@prefresh/rolldown` internally. Babel is only used as a selective fallback when +you pass `parserPlugins`. + ## Options The plugin accepts two options `include` & `exclude` which are used in the [`@rollup/pluginutils.createFilter`](https://github.com/rollup/plugins/tree/master/packages/pluginutils#createfilter) to filter out files or include them. -The plugin also accepts the addition of [`parserPlugins`](https://babeljs.io/docs/en/babel-parser#plugins) +The plugin also accepts the addition of [`parserPlugins`](https://babeljs.io/docs/en/babel-parser#plugins). +Providing `parserPlugins` opts that file transform back into the Babel-based path, +similar to how `@preact/preset-vite` only enables Babel when that path is requested. ## Best practices @@ -32,7 +38,7 @@ The plugin also accepts the addition of [`parserPlugins`](https://babeljs.io/doc We need to be able to recognise your components, this means that components should start with a capital letter and hook should start with `use` followed by a capital letter. -This allows the Babel plugin to effectively recognise these. +This allows the refresh transform to effectively recognise these. Do note that a component as seen below is not named. diff --git a/packages/vite/index.d.ts b/packages/vite/index.d.ts index 671d8fa5..79140bb0 100644 --- a/packages/vite/index.d.ts +++ b/packages/vite/index.d.ts @@ -1,5 +1,5 @@ import { FilterPattern } from '@rollup/pluginutils'; -import { Plugin } from 'vite'; +import { PluginOption } from 'vite'; interface Options { parserPlugins?: readonly string[]; @@ -7,6 +7,6 @@ interface Options { exclude?: FilterPattern; } -declare const prefreshPlugin: (options?: Options) => Plugin; +declare const prefreshPlugin: (options?: Options) => Promise; export = prefreshPlugin; diff --git a/packages/vite/package.json b/packages/vite/package.json index df61eb73..0997df6f 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -37,14 +37,16 @@ "homepage": "https://github.com/preactjs/prefresh#readme", "dependencies": { "@babel/core": "^7.22.1", - "@prefresh/babel-plugin": "^0.5.2", "@prefresh/core": "^1.5.0", + "@prefresh/babel-plugin": "^0.5.2", + "@prefresh/rolldown": "workspace:*", "@prefresh/utils": "^1.2.0", - "@rollup/pluginutils": "^4.2.1" + "@rollup/pluginutils": "^4.2.1", + "rolldown": "^1.0.0-rc.12" }, "devDependencies": { "preact": "^10.26.10", - "vite": "^5.4.21" + "vite": "^8.0.0" }, "peerDependencies": { "preact": "^10.4.0 || ^11.0.0-0", diff --git a/packages/vite/src/index.js b/packages/vite/src/index.js index 69aba74a..25ecbcc1 100644 --- a/packages/vite/src/index.js +++ b/packages/vite/src/index.js @@ -1,49 +1,272 @@ -const { transformSync } = require('@babel/core'); const { createFilter } = require('@rollup/pluginutils'); -const prefreshBabelPlugin = require('@prefresh/babel-plugin'); + +const SCRIPT_LANG_RE = /\.(c|m)?(t|j)sx?$/; +let babel; +let prefreshRolldownPromise; +let viteSupportsHookFilters; +let viteVersionParts; + +function loadBabel() { + babel ||= { + transformSync: require('@babel/core').transformSync, + prefreshBabelPlugin: require('@prefresh/babel-plugin'), + }; + return babel; +} + +function loadPrefreshRolldown() { + prefreshRolldownPromise ||= import('@prefresh/rolldown').then( + ({ default: prefreshRolldown }) => prefreshRolldown + ); + return prefreshRolldownPromise; +} + +function supportsHookFilters() { + if (viteSupportsHookFilters !== undefined) return viteSupportsHookFilters; + + const [major, minor] = getViteVersionParts(); + viteSupportsHookFilters = major > 6 || (major === 6 && minor >= 3); + + return viteSupportsHookFilters; +} + +function getViteVersionParts() { + if (viteVersionParts) return viteVersionParts; + + try { + viteVersionParts = require('vite/package.json') + .version.split('.') + .map(Number); + } catch (error) { + viteVersionParts = [0, 0, 0]; + } + + return viteVersionParts; +} + +function supportsRolldownVite() { + const [major] = getViteVersionParts(); + return major >= 8; +} + +function withScriptHookFilter(handler) { + return supportsHookFilters() + ? { + filter: { + id: SCRIPT_LANG_RE, + }, + handler, + } + : handler; +} + +function getEsbuildOptions(config) { + return config.esbuild && typeof config.esbuild === 'object' + ? config.esbuild + : {}; +} + +function getOxcOptions(config) { + return config.oxc && typeof config.oxc === 'object' ? config.oxc : {}; +} + +function stripHandledEsbuildOptions(config) { + const esbuild = getEsbuildOptions(config); + + if (!Object.keys(esbuild).length) return config.esbuild; + + const { jsx, jsxFactory, jsxFragment, jsxImportSource, jsxInject, ...rest } = + esbuild; + + return Object.keys(rest).length ? rest : false; +} + +function resolveJsxRuntime(jsx, esbuild) { + if (jsx.runtime) return jsx.runtime; + if (esbuild.jsx === 'automatic') return 'automatic'; + if (esbuild.jsx === 'preserve') return 'preserve'; + return 'classic'; +} + +function resolvePrefreshJsxOptions(jsx, esbuild) { + return { + ...jsx, + runtime: resolveJsxRuntime(jsx, esbuild), + pragma: jsx.pragma || esbuild.jsxFactory || 'h', + pragmaFrag: jsx.pragmaFrag || esbuild.jsxFragment || 'Fragment', + importSource: jsx.importSource || esbuild.jsxImportSource || 'preact', + }; +} + +function hasRolldownSupport(pluginContext) { + return !!( + pluginContext && + typeof pluginContext === 'object' && + pluginContext.meta && + typeof pluginContext.meta === 'object' && + 'rolldownVersion' in pluginContext.meta + ); +} + +/** @returns {Promise} */ +module.exports = async function prefreshPlugin(options = {}) { + const forceBabel = Object.prototype.hasOwnProperty.call( + options, + 'parserPlugins' + ); + const prefreshRolldown = supportsRolldownVite() + ? await loadPrefreshRolldown() + : null; + + return [ + preactOptionsPlugin(forceBabel), + prefreshBabelTransformPlugin(options, forceBabel), + ...(prefreshRolldown ? [prefreshRolldown()] : []), + prefreshWrapperPlugin(options), + ]; +}; + +/** @returns {import('vite').Plugin} */ +function preactOptionsPlugin(forceBabel) { + return { + name: 'prefresh-preact-options', + config(config, { command }) { + const oxc = getOxcOptions(config); + const jsx = oxc.jsx || {}; + const esbuild = getEsbuildOptions(config); + const prefreshJsx = resolvePrefreshJsxOptions(jsx, esbuild); + const optimizeDeps = config.optimizeDeps || {}; + const rolldownOptions = optimizeDeps.rolldownOptions || {}; + const transformOptions = rolldownOptions.transform || {}; + const supportsRolldown = hasRolldownSupport(this); + + return supportsRolldown + ? { + esbuild: stripHandledEsbuildOptions(config), + optimizeDeps: { + ...optimizeDeps, + rolldownOptions: { + ...rolldownOptions, + transform: { + ...transformOptions, + jsx: { + ...prefreshJsx, + }, + }, + }, + }, + oxc: { + ...oxc, + include: oxc.include || SCRIPT_LANG_RE, + jsxInject: oxc.jsxInject || esbuild.jsxInject, + jsx: { + ...prefreshJsx, + refresh: !forceBabel && command === 'serve', + }, + jsxRefreshInclude: oxc.jsxRefreshInclude || SCRIPT_LANG_RE, + }, + } + : {}; + }, + }; +} /** @returns {import('vite').Plugin} */ -module.exports = function prefreshPlugin(options = {}) { +function prefreshBabelTransformPlugin(options = {}, forceBabel) { let shouldSkip = false; const filter = createFilter(options.include, options.exclude); return { - name: 'prefresh', + name: 'prefresh-babel-transform', + apply: 'serve', configResolved(config) { - shouldSkip = - config.isProduction || - config.command === 'build' || - config.server.hmr === false; + shouldSkip = config.server.hmr === false; }, - async transform(code, id, options) { + transform: withScriptHookFilter(function (code, id, transformOptions) { + const useOxcRefresh = !forceBabel && hasRolldownSupport(this); const ssr = - typeof options === 'boolean' - ? options - : options && options.ssr === true; + typeof transformOptions === 'boolean' + ? transformOptions + : transformOptions && transformOptions.ssr === true; if ( shouldSkip || - !/\.(c|m)?(t|j)sx?$/.test(id) || + !SCRIPT_LANG_RE.test(id) || id.includes('node_modules') || id.includes('?worker') || !filter(id) || ssr - ) + ) { return; + } const parserPlugins = [ 'jsx', 'classProperties', 'classPrivateProperties', 'classPrivateMethods', - /\.tsx?$/.test(id) && 'typescript', + /\.(c|m)?tsx?$/.test(id) && 'typescript', ...((options && options.parserPlugins) || []), ].filter(Boolean); + if (useOxcRefresh) { + const hasReg = /\$RefreshReg\$\(/.test(code); + const hasSig = /\$RefreshSig\$\(/.test(code); + + if (hasReg || hasSig) return; + } + const result = transform(code, id, parserPlugins); - const hasReg = /\$RefreshReg\$\(/.test(result.code); - const hasSig = /\$RefreshSig\$\(/.test(result.code); - if (!hasSig && !hasReg) return code; + if (useOxcRefresh) { + const hasReg = /\$RefreshReg\$\(/.test(result.code); + const hasSig = /\$RefreshSig\$\(/.test(result.code); + + if (!hasReg && !hasSig) return; + } + + return result; + }), + }; +} + +/** @returns {import('vite').Plugin} */ +function prefreshWrapperPlugin(options = {}) { + let shouldSkip = false; + const filter = createFilter(options.include, options.exclude); + + return { + name: 'prefresh-wrapper', + apply: 'serve', + config() { + return { + optimizeDeps: { + include: ['@prefresh/core', '@prefresh/utils'], + }, + }; + }, + configResolved(config) { + shouldSkip = config.server.hmr === false; + }, + transform: withScriptHookFilter(async function (code, id, options) { + const ssr = + typeof options === 'boolean' + ? options + : options && options.ssr === true; + if ( + shouldSkip || + !SCRIPT_LANG_RE.test(id) || + id.includes('node_modules') || + id.includes('?worker') || + !filter(id) || + ssr + ) { + return; + } + + const hasReg = /\$RefreshReg\$\(/.test(code); + const hasSig = /\$RefreshSig\$\(/.test(code); + + if (!hasSig && !hasReg) return; const prefreshCore = await this.resolve('@prefresh/core', __filename); const prefreshUtils = await this.resolve('@prefresh/utils', __filename); @@ -79,13 +302,13 @@ module.exports = function prefreshPlugin(options = {}) { if (hasSig && !hasReg) { return { - code: `${prelude}${result.code}`, - map: result.map, + code: `${prelude}${code}`, + map: null, }; } return { - code: `${prelude}${result.code} + code: `${prelude}${code} if (import.meta.hot) { self.$RefreshReg$ = prevRefreshReg; @@ -100,14 +323,16 @@ module.exports = function prefreshPlugin(options = {}) { }); } `, - map: result.map, + map: null, }; - }, + }), }; -}; +} -const transform = (code, path, plugins) => - transformSync(code, { +const transform = (code, path, plugins) => { + const { transformSync, prefreshBabelPlugin } = loadBabel(); + + return transformSync(code, { plugins: [[prefreshBabelPlugin, { skipEnvCheck: true }]], parserOpts: { plugins, @@ -119,3 +344,4 @@ const transform = (code, path, plugins) => configFile: false, babelrc: false, }); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f76ba92..00dc1a2c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -112,19 +112,25 @@ importers: '@prefresh/core': specifier: ^1.5.0 version: 1.5.4(preact@10.26.10) + '@prefresh/rolldown': + specifier: workspace:* + version: link:../rolldown '@prefresh/utils': specifier: ^1.2.0 version: 1.2.1 '@rollup/pluginutils': specifier: ^4.2.1 version: 4.2.1 + rolldown: + specifier: ^1.0.0-rc.12 + version: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) devDependencies: preact: specifier: ^10.26.10 version: 10.26.10 vite: - specifier: ^5.4.21 - version: 5.4.21(@types/node@24.3.0)(lightningcss@1.32.0) + specifier: ^8.0.0 + version: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.3.0) packages/web-dev-server: dependencies: @@ -420,144 +426,6 @@ packages: '@emnapi/wasi-threads@1.2.0': resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1554,6 +1422,7 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.0, please upgrade batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} @@ -2210,11 +2079,6 @@ packages: es6-promise@4.2.8: resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2642,7 +2506,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -4003,10 +3867,6 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -4931,37 +4791,6 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - vite@8.0.3: resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5585,75 +5414,6 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@8.42.0)': dependencies: eslint: 8.42.0 @@ -7692,32 +7452,6 @@ snapshots: es6-promise@4.2.8: {} - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - escalade@3.2.0: {} escape-html@1.0.3: {} @@ -9898,12 +9632,6 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postcss@8.5.8: dependencies: nanoid: 3.3.11 @@ -11017,16 +10745,6 @@ snapshots: vary@1.1.2: {} - vite@5.4.21(@types/node@24.3.0)(lightningcss@1.32.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.6 - rollup: 4.52.5 - optionalDependencies: - '@types/node': 24.3.0 - fsevents: 2.3.3 - lightningcss: 1.32.0 - vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.3.0): dependencies: lightningcss: 1.32.0 diff --git a/test/constants.js b/test/constants.js index 3e268205..6f2463bb 100644 --- a/test/constants.js +++ b/test/constants.js @@ -1,28 +1,42 @@ const path = require('path'); -exports.integrations = ['vite']; +exports.integrations = ['vite', 'vite-babel']; exports.bin = { vite: dir => path.resolve(dir, `./node_modules/vite/bin/vite.js`), + 'vite-babel': dir => path.resolve(dir, `./node_modules/vite/bin/vite.js`), 'vite-preact-compat': dir => path.resolve(dir, `./node_modules/vite/bin/vite.js`), + 'vite-preact-compat-babel': dir => + path.resolve(dir, `./node_modules/vite/bin/vite.js`), 'vite-signals': dir => path.resolve(dir, `./node_modules/vite/bin/vite.js`), + 'vite-signals-babel': dir => + path.resolve(dir, `./node_modules/vite/bin/vite.js`), }; exports.binArgs = { 'vite-preact-compat': [], + 'vite-preact-compat-babel': [], 'vite-signals': [], + 'vite-signals-babel': [], vite: [], + 'vite-babel': [], }; exports.goMessage = { vite: 'ready', + 'vite-babel': 'ready', 'vite-preact-compat': 'ready', + 'vite-preact-compat-babel': 'ready', 'vite-signals': 'ready', + 'vite-signals-babel': 'ready', }; exports.defaultPort = { vite: 3000, + 'vite-babel': 3001, 'vite-preact-compat': 3002, + 'vite-preact-compat-babel': 3003, 'vite-signals': 3007, + 'vite-signals-babel': 3008, }; diff --git a/test/fixture/vite-babel/index.html b/test/fixture/vite-babel/index.html new file mode 100644 index 00000000..0de7aa3f --- /dev/null +++ b/test/fixture/vite-babel/index.html @@ -0,0 +1,11 @@ + + + + + Vite App + + +
+ + + diff --git a/test/fixture/vite-babel/main.jsx b/test/fixture/vite-babel/main.jsx new file mode 100644 index 00000000..4b247c4b --- /dev/null +++ b/test/fixture/vite-babel/main.jsx @@ -0,0 +1,4 @@ +import { render, h } from 'preact' +import { App } from './src/app' + +render(, document.getElementById('app')) diff --git a/test/fixture/vite-babel/package.json b/test/fixture/vite-babel/package.json new file mode 100644 index 00000000..97e52675 --- /dev/null +++ b/test/fixture/vite-babel/package.json @@ -0,0 +1,21 @@ +{ + "scripts": { + "dev": "vite" + }, + "dependencies": { + "goober": "^2.0.36", + "preact": "^10.19.2" + }, + "devDependencies": { + "@babel/core": "^7.22.1", + "@prefresh/babel-plugin": "file:../../packages/babel", + "@prefresh/vite": "file:../../packages/vite", + "rolldown": "^1.0.0-rc.12", + "vite": "^5.0.0" + }, + "resolutions": { + "@prefresh/rolldown": "file:../../packages/rolldown", + "@prefresh/core": "file:../../packages/core", + "@prefresh/utils": "file:../../packages/utils" + } +} diff --git a/test/fixture/vite-babel/src/app.jsx b/test/fixture/vite-babel/src/app.jsx new file mode 100644 index 00000000..ef3981c5 --- /dev/null +++ b/test/fixture/vite-babel/src/app.jsx @@ -0,0 +1,35 @@ +import { h } from 'preact'; +import { useCounter } from './useCounter'; +import { StoreProvider } from './context'; +import { Products } from './products'; +import { Greeting } from './greeting'; +import { Effect } from './effect'; +import { GenericContext } from './genericCtx'; +import { setup } from 'goober'; +import { Style } from './styles'; + +setup(h); + +function Test() { + const [count, increment] = useCounter(); + return ( +
+

Count: {count}

+ +
+ ) +} + +export function App(props) { + return ( + + ) +} diff --git a/test/fixture/vite-babel/src/context.jsx b/test/fixture/vite-babel/src/context.jsx new file mode 100644 index 00000000..4ca0a6ab --- /dev/null +++ b/test/fixture/vite-babel/src/context.jsx @@ -0,0 +1,25 @@ +import { createContext, h } from 'preact'; +import { useState } from 'preact/hooks'; + +export const StoreContext = createContext(); + +export const StoreProvider = ({ children }) => { + const [items, setItems] = useState([]); + + const addItem = (id) => { + if (!items.includes(id)) setItems([...items, id]); + } + + const removeItem = (id) => { + if (items.includes(id)) setItems(items.filter(itemId => itemId !== id)); + } + + return ( + +
    + {items.map(id =>
  • {id}
  • )} +
+ {children} +
+ ) +} diff --git a/test/fixture/vite-babel/src/effect.jsx b/test/fixture/vite-babel/src/effect.jsx new file mode 100644 index 00000000..a9d966be --- /dev/null +++ b/test/fixture/vite-babel/src/effect.jsx @@ -0,0 +1,10 @@ +import { h } from 'preact'; +import { useEffect, useState } from 'preact/hooks'; + +export const Effect = () => { + const [state, setState] = useState('hello world'); + + useEffect(() => { setState('hello world'); }, []); + + return

{state}

+} diff --git a/test/fixture/vite-babel/src/genericCtx.jsx b/test/fixture/vite-babel/src/genericCtx.jsx new file mode 100644 index 00000000..87333491 --- /dev/null +++ b/test/fixture/vite-babel/src/genericCtx.jsx @@ -0,0 +1,28 @@ +import { h } from 'preact'; +import { createContextWithoutDefault } from './helpers'; + +const [ContextA, useContextA] = + createContextWithoutDefault('Context A Error'); +const [ContextB, useContextB] = + createContextWithoutDefault('Context B Error'); + +export function GenericContext() { + return ( + + + + ); +} + +const Test = () => { + let contextB; + try { + // Expect this to throw. + contextB = useContextB(); + } catch (e) { + return

Correct behavior: {e.message}

; + } + + // Instead it has the value of "Context A"! + return

{contextB}

; +}; diff --git a/test/fixture/vite-babel/src/greeting.jsx b/test/fixture/vite-babel/src/greeting.jsx new file mode 100644 index 00000000..58ddccf7 --- /dev/null +++ b/test/fixture/vite-babel/src/greeting.jsx @@ -0,0 +1,24 @@ +import { Component, h } from 'preact' + +export class Greeting extends Component { + constructor(props) { + super(props); + this.state = { greeting: 'hi' }; + this.setGreeting = this.setGreeting.bind(this); + } + + setGreeting() { + this.setState({ greeting: 'bye' }); + } + + render() { + return ( +
+

I'm a class component

+

{this.state.greeting}

+ +
+ ) + } + +} diff --git a/test/fixture/vite-babel/src/helpers.js b/test/fixture/vite-babel/src/helpers.js new file mode 100644 index 00000000..d7559188 --- /dev/null +++ b/test/fixture/vite-babel/src/helpers.js @@ -0,0 +1,15 @@ +import { createContext } from 'preact'; +import { useContext } from 'preact/hooks'; + +export const createContextWithoutDefault = (errorMessage) => { + const emptyContext = Symbol(); + const context = createContext(emptyContext); + const useCtx = () => { + const ctx = useContext(context); + if ((ctx) === emptyContext) { + throw new Error(errorMessage); + } + return ctx; + }; + return [context, useCtx]; +}; diff --git a/test/fixture/vite-babel/src/products.jsx b/test/fixture/vite-babel/src/products.jsx new file mode 100644 index 00000000..71c30bed --- /dev/null +++ b/test/fixture/vite-babel/src/products.jsx @@ -0,0 +1,14 @@ +import { h } from 'preact'; +import { useContext } from 'preact/hooks'; +import { StoreContext } from './context'; + +const products = ['apple', 'peach'] + +export const Products = () => { + const { addItem, removeItem, items } = useContext(StoreContext); + return products.map(id => ( +
items.includes(id) ? removeItem(id) : addItem(id)}> + {id} +
+ )) +} diff --git a/test/fixture/vite-babel/src/styles.js b/test/fixture/vite-babel/src/styles.js new file mode 100644 index 00000000..424afca3 --- /dev/null +++ b/test/fixture/vite-babel/src/styles.js @@ -0,0 +1,5 @@ +import { styled } from 'goober'; + +export const Style = styled('div')` + background-color: #000; +`; diff --git a/test/fixture/vite-babel/src/useCounter.js b/test/fixture/vite-babel/src/useCounter.js new file mode 100644 index 00000000..8ccc2dc4 --- /dev/null +++ b/test/fixture/vite-babel/src/useCounter.js @@ -0,0 +1,6 @@ +import { useState } from 'preact/hooks'; + +export const useCounter = () => { + const [state, setState] = useState(0); + return [state, () => setState(state + 1)]; +}; diff --git a/test/fixture/vite-babel/vite.config.js b/test/fixture/vite-babel/vite.config.js new file mode 100644 index 00000000..80d80997 --- /dev/null +++ b/test/fixture/vite-babel/vite.config.js @@ -0,0 +1,15 @@ +import prefresh from '@prefresh/vite'; + +export default { + server: { + port: 3001, + }, + esbuild: { + jsxFactory: 'h', + jsxFragment: 'Fragment' + }, + optimizeDeps: { + include: ['preact/hooks'] + }, + plugins: [prefresh()] +}; diff --git a/test/fixture/vite-preact-compat-babel/index.html b/test/fixture/vite-preact-compat-babel/index.html new file mode 100644 index 00000000..0de7aa3f --- /dev/null +++ b/test/fixture/vite-preact-compat-babel/index.html @@ -0,0 +1,11 @@ + + + + + Vite App + + +
+ + + diff --git a/test/fixture/vite-preact-compat-babel/main.jsx b/test/fixture/vite-preact-compat-babel/main.jsx new file mode 100644 index 00000000..47fd939e --- /dev/null +++ b/test/fixture/vite-preact-compat-babel/main.jsx @@ -0,0 +1,4 @@ +import { render, createElement as h } from 'preact/compat' +import { App } from './src/app' + +render(, document.getElementById('app')) diff --git a/test/fixture/vite-preact-compat-babel/package.json b/test/fixture/vite-preact-compat-babel/package.json new file mode 100644 index 00000000..97e52675 --- /dev/null +++ b/test/fixture/vite-preact-compat-babel/package.json @@ -0,0 +1,21 @@ +{ + "scripts": { + "dev": "vite" + }, + "dependencies": { + "goober": "^2.0.36", + "preact": "^10.19.2" + }, + "devDependencies": { + "@babel/core": "^7.22.1", + "@prefresh/babel-plugin": "file:../../packages/babel", + "@prefresh/vite": "file:../../packages/vite", + "rolldown": "^1.0.0-rc.12", + "vite": "^5.0.0" + }, + "resolutions": { + "@prefresh/rolldown": "file:../../packages/rolldown", + "@prefresh/core": "file:../../packages/core", + "@prefresh/utils": "file:../../packages/utils" + } +} diff --git a/test/fixture/vite-preact-compat-babel/src/app.jsx b/test/fixture/vite-preact-compat-babel/src/app.jsx new file mode 100644 index 00000000..18248673 --- /dev/null +++ b/test/fixture/vite-preact-compat-babel/src/app.jsx @@ -0,0 +1,21 @@ +import { lazy, Suspense, useState, createElement as h, Fragment } from 'preact/compat'; + +const Test1Lazy = lazy(() => import('./test1.jsx')); +const Test2Lazy = lazy(() => import('./test2.jsx')); + +export function App() { + const [show, setShow] = useState(true); + + const toggle = () => { + setShow(!show); + }; + + return ( + <> + Loading}> + {show ? : } + + + + ); +} diff --git a/test/fixture/vite-preact-compat-babel/src/test1.jsx b/test/fixture/vite-preact-compat-babel/src/test1.jsx new file mode 100644 index 00000000..2a840576 --- /dev/null +++ b/test/fixture/vite-preact-compat-babel/src/test1.jsx @@ -0,0 +1,5 @@ +import { createElement as h } from 'preact/compat'; + +export default function Test1() { + return

Test 1

; +} diff --git a/test/fixture/vite-preact-compat-babel/src/test2.jsx b/test/fixture/vite-preact-compat-babel/src/test2.jsx new file mode 100644 index 00000000..9861d0f8 --- /dev/null +++ b/test/fixture/vite-preact-compat-babel/src/test2.jsx @@ -0,0 +1,5 @@ +import { createElement as h } from 'preact/compat'; + +export default function Test2() { + return

Test 2

; +} diff --git a/test/fixture/vite-preact-compat-babel/vite.config.js b/test/fixture/vite-preact-compat-babel/vite.config.js new file mode 100644 index 00000000..b6554d88 --- /dev/null +++ b/test/fixture/vite-preact-compat-babel/vite.config.js @@ -0,0 +1,15 @@ +import prefresh from '@prefresh/vite'; + +export default { + server: { + port: 3003, + }, + esbuild: { + jsxFactory: 'h', + jsxFragment: 'Fragment' + }, + optimizeDeps: { + include: ['preact/hooks', 'preact/compat', 'preact'] + }, + plugins: [prefresh()] +}; diff --git a/test/fixture/vite-preact-compat/package.json b/test/fixture/vite-preact-compat/package.json index 0457e1a9..a0e801d5 100644 --- a/test/fixture/vite-preact-compat/package.json +++ b/test/fixture/vite-preact-compat/package.json @@ -7,13 +7,13 @@ "preact": "^10.19.2" }, "devDependencies": { - "@babel/core": "^7.22.1", - "@prefresh/babel-plugin": "file:../../packages/babel", "@prefresh/vite": "file:../../packages/vite", - "vite": "^5.0.0" + "rolldown": "^1.0.0-rc.12", + "vite": "^8.0.0" }, "resolutions": { - "@prefresh/core": "file:../../packages/core", - "@prefresh/utils": "file:../../packages/utils" + "@prefresh/rolldown": "file:../../packages/rolldown", + "@prefresh/core": "file:../../packages/core", + "@prefresh/utils": "file:../../packages/utils" } } diff --git a/test/fixture/vite-preact-compat/vite.config.js b/test/fixture/vite-preact-compat/vite.config.js index 224615fe..430159ff 100644 --- a/test/fixture/vite-preact-compat/vite.config.js +++ b/test/fixture/vite-preact-compat/vite.config.js @@ -4,7 +4,7 @@ export default { server: { port: 3002, }, - esbuild: { + oxc: { jsxFactory: 'h', jsxFragment: 'Fragment' }, diff --git a/test/fixture/vite-signals-babel/index.html b/test/fixture/vite-signals-babel/index.html new file mode 100644 index 00000000..0de7aa3f --- /dev/null +++ b/test/fixture/vite-signals-babel/index.html @@ -0,0 +1,11 @@ + + + + + Vite App + + +
+ + + diff --git a/test/fixture/vite-signals-babel/main.jsx b/test/fixture/vite-signals-babel/main.jsx new file mode 100644 index 00000000..4b247c4b --- /dev/null +++ b/test/fixture/vite-signals-babel/main.jsx @@ -0,0 +1,4 @@ +import { render, h } from 'preact' +import { App } from './src/app' + +render(, document.getElementById('app')) diff --git a/test/fixture/vite-signals-babel/package.json b/test/fixture/vite-signals-babel/package.json new file mode 100644 index 00000000..927031cb --- /dev/null +++ b/test/fixture/vite-signals-babel/package.json @@ -0,0 +1,21 @@ +{ + "scripts": { + "dev": "vite" + }, + "dependencies": { + "@preact/signals": "^2.0.0", + "preact": "^10.19.2" + }, + "devDependencies": { + "@babel/core": "^7.22.1", + "@prefresh/babel-plugin": "file:../../packages/babel", + "@prefresh/vite": "file:../../packages/vite", + "rolldown": "^1.0.0-rc.12", + "vite": "^5.0.0" + }, + "resolutions": { + "@prefresh/rolldown": "file:../../packages/rolldown", + "@prefresh/core": "file:../../packages/core", + "@prefresh/utils": "file:../../packages/utils" + } +} diff --git a/test/fixture/vite-signals-babel/src/Input.jsx b/test/fixture/vite-signals-babel/src/Input.jsx new file mode 100644 index 00000000..b3ed6a03 --- /dev/null +++ b/test/fixture/vite-signals-babel/src/Input.jsx @@ -0,0 +1,9 @@ +import { h } from 'preact'; +import { signal } from '@preact/signals'; + +const inputValue = signal('foo') +export function Input() { + return ( + { inputValue.value = e.currentTarget.value }} /> + ) +} diff --git a/test/fixture/vite-signals-babel/src/app.jsx b/test/fixture/vite-signals-babel/src/app.jsx new file mode 100644 index 00000000..62a882df --- /dev/null +++ b/test/fixture/vite-signals-babel/src/app.jsx @@ -0,0 +1,22 @@ +import { h } from 'preact'; +import { signal } from '@preact/signals'; +import { Input } from './Input.jsx' + +const count = signal(0) +function Counter() { + return ( +
+

Count: {count}

+ +
+ ) +} + +export function App() { + return ( +
+ + +
+ ) +} diff --git a/test/fixture/vite-signals-babel/vite.config.js b/test/fixture/vite-signals-babel/vite.config.js new file mode 100644 index 00000000..8aeaab8a --- /dev/null +++ b/test/fixture/vite-signals-babel/vite.config.js @@ -0,0 +1,15 @@ +import prefresh from '@prefresh/vite'; + +export default { + server: { + port: 3008 + }, + esbuild: { + jsxFactory: 'h', + jsxFragment: 'Fragment' + }, + optimizeDeps: { + include: ['preact/hooks'] + }, + plugins: [prefresh()] +}; diff --git a/test/fixture/vite-signals/package.json b/test/fixture/vite-signals/package.json index 051ff64c..6239ece6 100644 --- a/test/fixture/vite-signals/package.json +++ b/test/fixture/vite-signals/package.json @@ -7,12 +7,12 @@ "preact": "^10.19.2" }, "devDependencies": { - "@babel/core": "^7.22.1", - "@prefresh/babel-plugin": "file:../../packages/babel", "@prefresh/vite": "file:../../packages/vite", - "vite": "^5.0.0" + "rolldown": "^1.0.0-rc.12", + "vite": "^8.0.0" }, "resolutions": { + "@prefresh/rolldown": "file:../../packages/rolldown", "@prefresh/core": "file:../../packages/core", "@prefresh/utils": "file:../../packages/utils" } diff --git a/test/fixture/vite-signals/vite.config.js b/test/fixture/vite-signals/vite.config.js index c914d2b8..9c6f37d2 100644 --- a/test/fixture/vite-signals/vite.config.js +++ b/test/fixture/vite-signals/vite.config.js @@ -4,7 +4,7 @@ export default { server: { port: 3007 }, - esbuild: { + oxc: { jsxFactory: 'h', jsxFragment: 'Fragment' }, diff --git a/test/fixture/vite/package.json b/test/fixture/vite/package.json index 0457e1a9..a0e801d5 100644 --- a/test/fixture/vite/package.json +++ b/test/fixture/vite/package.json @@ -7,13 +7,13 @@ "preact": "^10.19.2" }, "devDependencies": { - "@babel/core": "^7.22.1", - "@prefresh/babel-plugin": "file:../../packages/babel", "@prefresh/vite": "file:../../packages/vite", - "vite": "^5.0.0" + "rolldown": "^1.0.0-rc.12", + "vite": "^8.0.0" }, "resolutions": { - "@prefresh/core": "file:../../packages/core", - "@prefresh/utils": "file:../../packages/utils" + "@prefresh/rolldown": "file:../../packages/rolldown", + "@prefresh/core": "file:../../packages/core", + "@prefresh/utils": "file:../../packages/utils" } } diff --git a/test/fixture/vite/vite.config.js b/test/fixture/vite/vite.config.js index 9af1e0ec..2c8cfe40 100644 --- a/test/fixture/vite/vite.config.js +++ b/test/fixture/vite/vite.config.js @@ -4,7 +4,7 @@ export default { server: { port: 3000, }, - esbuild: { + oxc: { jsxFactory: 'h', jsxFragment: 'Fragment' }, diff --git a/test/signals.test.js b/test/signals.test.js index fd672273..6c066667 100644 --- a/test/signals.test.js +++ b/test/signals.test.js @@ -13,162 +13,166 @@ const { bin, binArgs, goMessage, defaultPort } = require('./constants'); const TIMEOUT = 1000; describe('Signals', () => { - const integration = 'vite-signals'; - let devServer, browser, page, serverConsoleListener; - - const browserConsoleListener = msg => { - console.log('[BROWSER LOG]: ', msg); - }; - - jest.setTimeout(100000); - - afterAll(async () => { - if (process.env.DEBUG) - page.removeListener('console', browserConsoleListener); - - if (browser) await browser.close(); - if (devServer) { - devServer.kill('SIGTERM', { - forceKillAfterTimeout: 0, + ['vite-signals', 'vite-signals-babel'].forEach(integration => { + let devServer, browser, page, serverConsoleListener; + + const browserConsoleListener = msg => { + console.log('[BROWSER LOG]: ', msg); + }; + + jest.setTimeout(100000); + + describe(integration, () => { + afterAll(async () => { + if (process.env.DEBUG) + page.removeListener('console', browserConsoleListener); + + if (browser) await browser.close(); + if (devServer) { + devServer.kill('SIGTERM', { + forceKillAfterTimeout: 0, + }); + } + + try { + await fs.remove(getTempDir(integration)); + } catch (e) {} }); - } - try { - await fs.remove(getTempDir(integration)); - } catch (e) {} - }); + beforeAll(async () => { + await timeout(2000); + try { + await fs.remove(getTempDir(integration)); + } catch (e) {} + + await fs.copy(getFixtureDir(integration), getTempDir(integration), { + filter: file => !/dist|node_modules/.test(file), + }); + + await execa('yarn', { cwd: getTempDir(integration) }); + + browser = await puppeteer.launch({ + headless: 'new', + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }); + page = await browser.newPage(); + + devServer = execa( + bin[integration](getTempDir(integration)), + binArgs[integration], + { + cwd: getTempDir(integration), + } + ); + + await new Promise(resolve => { + devServer.stdout.on( + 'data', + (serverConsoleListener = data => { + if (process.env.DEBUG) + console.log('[SERVER LOG]: ', data.toString()); + if (data.toString().match(goMessage[integration])) { + resolve(); + } + }) + ); + + devServer.stderr.on( + 'data', + (serverConsoleListener = data => { + console.log('[ERROR SERVER LOG]: ', data.toString()); + }) + ); + }); + + page = await browser.newPage(); + if (process.env.DEBUG) page.on('console', browserConsoleListener); + + await page.goto('http://localhost:' + defaultPort[integration]); + }); - beforeAll(async () => { - await timeout(2000); - try { - await fs.remove(getTempDir(integration)); - } catch (e) {} + const getEl = async selectorOrEl => { + return typeof selectorOrEl === 'string' + ? await page.$(selectorOrEl) + : selectorOrEl; + }; + + const getText = async selectorOrEl => { + const el = await getEl(selectorOrEl); + return el ? el.evaluate(el => el.textContent) : null; + }; + + const getInputValue = async selectorOrEl => { + const el = await getEl(selectorOrEl); + return el ? el.evaluate(el => el.value) : null; + }; + + async function updateFile(file, replacer) { + const compPath = path.join(getTempDir(integration), file); + const content = await fs.readFile(compPath, 'utf-8'); + await fs.writeFile(compPath, replacer(content)); + } - await fs.copy(getFixtureDir(integration), getTempDir(integration), { - filter: file => !/dist|node_modules/.test(file), - }); + test('Can increment after HMR', async () => { + const increment = await page.$('.increment'); + const countValue = await page.$('.value'); + await expectByPolling(() => getText(countValue), 'Count: 0'); + await increment.click(); + await expectByPolling(() => getText(countValue), 'Count: 1'); - await execa('yarn', { cwd: getTempDir(integration) }); + await updateFile('src/app.jsx', content => + content.replace('Count:', 'count:') + ); - browser = await puppeteer.launch({ - headless: 'new', - args: ['--no-sandbox', '--disable-setuid-sandbox'], - }); - page = await browser.newPage(); + await timeout(TIMEOUT); + await expectByPolling(() => getText(countValue), 'count: 0'); - devServer = execa( - bin[integration](getTempDir(integration)), - binArgs[integration], - { - cwd: getTempDir(integration), - } - ); - - await new Promise(resolve => { - devServer.stdout.on( - 'data', - (serverConsoleListener = data => { - if (process.env.DEBUG) console.log('[SERVER LOG]: ', data.toString()); - if (data.toString().match(goMessage[integration])) { - resolve(); - } - }) - ); - - devServer.stderr.on( - 'data', - (serverConsoleListener = data => { - console.log('[ERROR SERVER LOG]: ', data.toString()); - }) - ); - }); + await increment.click(); + await increment.click(); + await increment.click(); - page = await browser.newPage(); - if (process.env.DEBUG) page.on('console', browserConsoleListener); + await expectByPolling(() => getText(countValue), 'count: 3'); - await page.goto('http://localhost:' + defaultPort[integration]); - }); + await updateFile('src/app.jsx', content => + content.replace('count:', 'Count:') + ); + await timeout(TIMEOUT); + await expectByPolling(() => getText(countValue), 'Count: 0'); - const getEl = async selectorOrEl => { - return typeof selectorOrEl === 'string' - ? await page.$(selectorOrEl) - : selectorOrEl; - }; - - const getText = async selectorOrEl => { - const el = await getEl(selectorOrEl); - return el ? el.evaluate(el => el.textContent) : null; - }; - - const getInputValue = async selectorOrEl => { - const el = await getEl(selectorOrEl); - return el ? el.evaluate(el => el.value) : null; - }; - - async function updateFile(file, replacer) { - const compPath = path.join(getTempDir(integration), file); - const content = await fs.readFile(compPath, 'utf-8'); - await fs.writeFile(compPath, replacer(content)); - } - - test('Can increment after HMR', async () => { - const increment = await page.$('.increment'); - const countValue = await page.$('.value'); - await expectByPolling(() => getText(countValue), 'Count: 0'); - await increment.click(); - await expectByPolling(() => getText(countValue), 'Count: 1'); - - await updateFile('src/app.jsx', content => - content.replace('Count:', 'count:') - ); - - await timeout(TIMEOUT); - await expectByPolling(() => getText(countValue), 'count: 0'); - - await increment.click(); - await increment.click(); - await increment.click(); - - await expectByPolling(() => getText(countValue), 'count: 3'); - - await updateFile('src/app.jsx', content => - content.replace('count:', 'Count:') - ); - await timeout(TIMEOUT); - await expectByPolling(() => getText(countValue), 'Count: 0'); - - await increment.click(); - await expectByPolling(() => getText(countValue), 'Count: 1'); - }); + await increment.click(); + await expectByPolling(() => getText(countValue), 'Count: 1'); + }); - test('Reacts to adjusting the initial value', async () => { - const countValue = await page.$('.value'); + test('Reacts to adjusting the initial value', async () => { + const countValue = await page.$('.value'); - await updateFile('src/app.jsx', content => - content.replace('signal(0)', 'signal(10)') - ); + await updateFile('src/app.jsx', content => + content.replace('signal(0)', 'signal(10)') + ); - await timeout(TIMEOUT); - await expectByPolling(() => getText(countValue), 'Count: 10'); - }); + await timeout(TIMEOUT); + await expectByPolling(() => getText(countValue), 'Count: 10'); + }); - test('Can change input values', async () => { - const input = await page.$('.input'); - await expectByPolling(() => getInputValue(input), 'foo'); + test('Can change input values', async () => { + const input = await page.$('.input'); + await expectByPolling(() => getInputValue(input), 'foo'); - await page.focus('.input'); - await page.keyboard.type('foooo'); - await expectByPolling(() => getInputValue(input), 'foooo'); + await page.focus('.input'); + await page.keyboard.type('foooo'); + await expectByPolling(() => getInputValue(input), 'foooo'); - await updateFile('src/Input.jsx', content => - content.replace("signal('foo')", "signal('bar')") - ); + await updateFile('src/Input.jsx', content => + content.replace("signal('foo')", "signal('bar')") + ); - await timeout(TIMEOUT); - //await expectByPolling(() => getInputValue(input), 'bar'); + await timeout(TIMEOUT); + //await expectByPolling(() => getInputValue(input), 'bar'); - await page.focus('.input'); - await page.keyboard.type('barfoo'); - await expectByPolling(() => getInputValue(input), 'barfoo'); + await page.focus('.input'); + await page.keyboard.type('barfoo'); + await expectByPolling(() => getInputValue(input), 'barfoo'); + }); + }); }); }); diff --git a/test/suspense.test.js b/test/suspense.test.js index 40aa86ea..7818b1b0 100644 --- a/test/suspense.test.js +++ b/test/suspense.test.js @@ -13,127 +13,131 @@ const { bin, binArgs, goMessage, defaultPort } = require('./constants'); const TIMEOUT = 1000; describe('Suspense', () => { - const integration = 'vite-preact-compat'; - let devServer, browser, page, serverConsoleListener; - - const browserConsoleListener = msg => { - console.log('[BROWSER LOG]: ', msg); - }; - - jest.setTimeout(100000); - - afterAll(async () => { - if (process.env.DEBUG) - page.removeListener('console', browserConsoleListener); - - if (browser) await browser.close(); - if (devServer) { - devServer.kill('SIGTERM', { - forceKillAfterTimeout: 0, + ['vite-preact-compat', 'vite-preact-compat-babel'].forEach(integration => { + let devServer, browser, page, serverConsoleListener; + + const browserConsoleListener = msg => { + console.log('[BROWSER LOG]: ', msg); + }; + + jest.setTimeout(100000); + + describe(integration, () => { + afterAll(async () => { + if (process.env.DEBUG) + page.removeListener('console', browserConsoleListener); + + if (browser) await browser.close(); + if (devServer) { + devServer.kill('SIGTERM', { + forceKillAfterTimeout: 0, + }); + } + + try { + await fs.remove(getTempDir(integration)); + } catch (e) {} }); - } - - try { - await fs.remove(getTempDir(integration)); - } catch (e) {} - }); - - beforeAll(async () => { - await timeout(2000); - try { - await fs.remove(getTempDir(integration)); - } catch (e) {} - - await fs.copy(getFixtureDir(integration), getTempDir(integration), { - filter: file => !/dist|node_modules/.test(file), - }); - - await execa('yarn', { cwd: getTempDir(integration) }); - - browser = await puppeteer.launch({ - headless: 'new', - args: ['--no-sandbox', '--disable-setuid-sandbox'], - }); - page = await browser.newPage(); - devServer = execa( - bin[integration](getTempDir(integration)), - binArgs[integration], - { - cwd: getTempDir(integration), - } - ); - - await new Promise(resolve => { - devServer.stdout.on( - 'data', - (serverConsoleListener = data => { - if (process.env.DEBUG) console.log('[SERVER LOG]: ', data.toString()); - if (data.toString().match(goMessage[integration])) { - resolve(); + beforeAll(async () => { + await timeout(2000); + try { + await fs.remove(getTempDir(integration)); + } catch (e) {} + + await fs.copy(getFixtureDir(integration), getTempDir(integration), { + filter: file => !/dist|node_modules/.test(file), + }); + + await execa('yarn', { cwd: getTempDir(integration) }); + + browser = await puppeteer.launch({ + headless: 'new', + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }); + page = await browser.newPage(); + + devServer = execa( + bin[integration](getTempDir(integration)), + binArgs[integration], + { + cwd: getTempDir(integration), } - }) - ); - - devServer.stderr.on( - 'data', - (serverConsoleListener = data => { - console.log('[ERROR SERVER LOG]: ', data.toString()); - }) - ); - }); - - page = await browser.newPage(); - if (process.env.DEBUG) page.on('console', browserConsoleListener); - - await page.goto('http://localhost:' + defaultPort[integration]); - }); - - const getEl = async selectorOrEl => { - return typeof selectorOrEl === 'string' - ? await page.$(selectorOrEl) - : selectorOrEl; - }; - - const getText = async selectorOrEl => { - const el = await getEl(selectorOrEl); - return el ? el.evaluate(el => el.textContent) : null; - }; + ); + + await new Promise(resolve => { + devServer.stdout.on( + 'data', + (serverConsoleListener = data => { + if (process.env.DEBUG) + console.log('[SERVER LOG]: ', data.toString()); + if (data.toString().match(goMessage[integration])) { + resolve(); + } + }) + ); + + devServer.stderr.on( + 'data', + (serverConsoleListener = data => { + console.log('[ERROR SERVER LOG]: ', data.toString()); + }) + ); + }); + + page = await browser.newPage(); + if (process.env.DEBUG) page.on('console', browserConsoleListener); + + await page.goto('http://localhost:' + defaultPort[integration]); + }); - async function updateFile(file, replacer) { - const compPath = path.join(getTempDir(integration), file); - const content = await fs.readFile(compPath, 'utf-8'); - await fs.writeFile(compPath, replacer(content)); - } + const getEl = async selectorOrEl => { + return typeof selectorOrEl === 'string' + ? await page.$(selectorOrEl) + : selectorOrEl; + }; + + const getText = async selectorOrEl => { + const el = await getEl(selectorOrEl); + return el ? el.evaluate(el => el.textContent) : null; + }; + + async function updateFile(file, replacer) { + const compPath = path.join(getTempDir(integration), file); + const content = await fs.readFile(compPath, 'utf-8'); + await fs.writeFile(compPath, replacer(content)); + } - test('Can make change to lazy loaded components', async () => { - const button = await page.$('.toggle'); - let text1 = await page.$('.test-1'); - await expectByPolling(() => getText(text1), 'Test 1'); + test('Can make change to lazy loaded components', async () => { + const button = await page.$('.toggle'); + let text1 = await page.$('.test-1'); + await expectByPolling(() => getText(text1), 'Test 1'); - await updateFile('src/test1.jsx', content => - content.replace('Test 1', 'Test 1!!!') - ); + await updateFile('src/test1.jsx', content => + content.replace('Test 1', 'Test 1!!!') + ); - await timeout(TIMEOUT); - await expectByPolling(() => getText(text1), 'Test 1!!!'); + await timeout(TIMEOUT); + await expectByPolling(() => getText(text1), 'Test 1!!!'); - await button.click(); - await timeout(TIMEOUT); + await button.click(); + await timeout(TIMEOUT); - const text2 = await page.$('.test-2'); - await expectByPolling(() => getText(text2), 'Test 2'); + const text2 = await page.$('.test-2'); + await expectByPolling(() => getText(text2), 'Test 2'); - await button.click(); - await timeout(TIMEOUT); + await button.click(); + await timeout(TIMEOUT); - text1 = await page.$('.test-1'); - await expectByPolling(() => getText(text1), 'Test 1!!!'); + text1 = await page.$('.test-1'); + await expectByPolling(() => getText(text1), 'Test 1!!!'); - await updateFile('src/test1.jsx', content => - content.replace('Test 1!!!', 'Test 1!') - ); - await timeout(TIMEOUT); - await expectByPolling(() => getText(text1), 'Test 1!'); + await updateFile('src/test1.jsx', content => + content.replace('Test 1!!!', 'Test 1!') + ); + await timeout(TIMEOUT); + await expectByPolling(() => getText(text1), 'Test 1!'); + }); + }); }); });