diff --git a/adex/package.json b/adex/package.json index d7d076d..e1fcd6e 100644 --- a/adex/package.json +++ b/adex/package.json @@ -21,6 +21,9 @@ "types": "./src/ssr.d.ts", "import": "./src/ssr.js" }, + "./types": { + "types": "./types.d.ts" + }, "./head": { "types": "./src/head.d.ts", "import": "./src/head.js" @@ -61,11 +64,13 @@ "hoofd": "^1.7.1", "mri": "^1.2.0", "node-stream-zip": "^1.15.0", + "ofetch": "^1.4.1", "preact-render-to-string": "^6.5.5", "regexparam": "^3.0.0", "sirv": "^2.0.4", "trouter": "^4.0.0", - "unifont": "^0.0.2" + "unifont": "^0.0.2", + "unimport": "^4.0.0" }, "devDependencies": { "@preact/preset-vite": "^2.8.2", diff --git a/adex/runtime/fetch.js b/adex/runtime/fetch.js new file mode 100644 index 0000000..4076249 --- /dev/null +++ b/adex/runtime/fetch.js @@ -0,0 +1,16 @@ +import { ofetch as _ofetch } from 'ofetch' +import { env } from 'adex/env' + +const constructBaseURL = () => { + if (import.meta.server) { + const origin = env.get('HOST', 'localhost') + const port = env.get('PORT', '3000') + return new URL(`http:${origin}${port ? `:${port}` : ''}`).toString() + } + return window.location.toString() +} + +const baseURL = constructBaseURL() +export const $fetch = _ofetch.create({ + baseURL: baseURL, +}) diff --git a/adex/src/env.js b/adex/src/env.js index 7c328fc..a41cb7b 100644 --- a/adex/src/env.js +++ b/adex/src/env.js @@ -1,16 +1,17 @@ -const isClient = typeof window !== 'undefined' +export const env = {} -if (isClient) { - throw new Error('[adex] Cannot use/import `adex/env` on the client side') -} - -export const env = { - get(key, defaultValue = '') { - if (isClient) return '' +if (import.meta.server) { + env.get = (key, defaultValue = '') => { return process.env[key] ?? defaultValue - }, - set(key, value) { - if (isClient) return '' + } + env.set = (key, value) => { return (process.env[key] = value) - }, + } +} else { + env.get = (key, defaultValue = '') => { + return import.meta.env[key] ?? defaultValue + } + env.set = (key, value) => { + return (import.meta.env[key] = value) + } } diff --git a/adex/src/ssr.d.ts b/adex/src/ssr.d.ts index 7cff11b..2c61af8 100644 --- a/adex/src/ssr.d.ts +++ b/adex/src/ssr.d.ts @@ -3,4 +3,4 @@ export { renderToString } from 'preact-render-to-string' export { parse as pathToRegex } from 'regexparam' export { use as useMiddleware } from '@barelyhuman/tiny-use' export { default as sirv } from 'sirv' -export { default as mri } from 'mri' \ No newline at end of file +export { default as mri } from 'mri' diff --git a/adex/src/vite.js b/adex/src/vite.js index 7db7777..a8b607e 100644 --- a/adex/src/vite.js +++ b/adex/src/vite.js @@ -9,6 +9,7 @@ import { isFunctionIsland, readSourceFile, } from '@dumbjs/preland' +import Unimport from 'unimport/unplugin' import { addImportToAST, codeFromAST } from '@dumbjs/preland/ast' import preact from '@preact/preset-vite' import { mkdirSync, readFileSync, writeFileSync } from 'fs' @@ -37,6 +38,7 @@ export function adex({ adapter: adapter = 'node', } = {}) { return [ + adexConfig(), preactPages({ root: '/src/pages', id: '~routes', @@ -61,15 +63,15 @@ export function adex({ import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' import { existsSync, readFileSync } from 'node:fs' - import { env } from 'adex/env' + import {env} from "adex/env" import 'virtual:adex:font.css' import 'virtual:adex:global.css' const __dirname = dirname(fileURLToPath(import.meta.url)) - const PORT = parseInt(env.get('PORT', '3000'), 10) - const HOST = env.get('HOST', 'localhost') + const PORT = parseInt(env.get("PORT","3000")) + const HOST = env.get("HOST","localhost") const paths = { assets: join(__dirname, './assets'), @@ -128,7 +130,6 @@ export function adex({ adexServerBuilder({ islands }), !islands && adexClientBuilder(), islands && adexIslandsBuilder(), - ...adexGuards(), ] } @@ -212,6 +213,10 @@ function adexIslandsBuilder() { await build({ configFile: false, plugins: [preact()], + define: { + 'import.meta.server': false, + 'import.meta.client': true, + }, build: { ssr: false, outDir: join(outDir, 'islands'), @@ -346,6 +351,10 @@ function adexClientBuilder() { ), preact({ prefreshEnabled: false }), ], + define: { + 'import.meta.client': true, + 'import.meta.server': false, + }, build: { outDir: 'dist/client', emptyOutDir: true, @@ -388,8 +397,12 @@ function adexServerBuilder({ islands = false } = {}) { return { appType: 'custom', ssr: { - external: ['preact', 'adex', 'preact-render-to-string'], - noExternal: Object.values(adapterMap), + external: ['preact', 'preact-render-to-string'], + noExternal: Object.values(adapterMap).concat('adex/env'), + }, + define: { + 'import.meta.server': true, + 'import.meta.client': false, }, build: { outDir: 'dist/server', @@ -479,45 +492,32 @@ function adexServerBuilder({ islands = false } = {}) { /** * @returns {import("vite").Plugin[]} */ -function adexGuards() { +function adexConfig() { return [ + // @ts-expect-error something wrong wrong + Unimport.vite({ + imports: [ + { + name: '$fetch', + from: fileURLToPath(new URL('../runtime/fetch.js', import.meta.url)), + }, + ], + }), { - name: 'adex-guard-env', + name: 'adex-config', enforce: 'pre', - async transform(code, id) { - // ignore usage of `process.env` in node_modules - // Still risky but hard to do anything about - const nodeMods = resolve(cwd, 'node_modules') - if (id.startsWith(nodeMods)) return - - // ignore usage of `process.env` in `adex/env` - const envLoadId = await this.resolve('adex/env') - if (id === envLoadId.id) return - - if (code.includes('process.env')) { - this.error( - 'Avoid using `process.env` to access environment variables and secrets. Use `adex/env` instead' - ) - } - }, - writeBundle() { - const pagesPath = resolve(cwd, 'src/pages') - const info = this.getModuleInfo('adex/env') - const viteRef = this - - function checkTree(importPath, importStack = []) { - if (importPath.startsWith(pagesPath)) { - throw new Error( - `Cannot use/import \`adex/env\` on the client side, importerStack: ${importStack.join(' -> ')}` - ) - } - viteRef - .getModuleInfo(importPath) - .importers.forEach(d => - checkTree(d, [...importStack, importPath, d]) - ) + config() { + return { + server: { + port: 3000, + }, + define: { + 'import.meta.env.PORT': JSON.stringify(process.env.PORT ?? '3000'), + 'import.meta.env.HOST': JSON.stringify( + process.env.HOST ?? 'localhost' + ), + }, } - info.importers.forEach(i => checkTree(i)) }, }, ] diff --git a/adex/types.d.ts b/adex/types.d.ts new file mode 100644 index 0000000..3590642 --- /dev/null +++ b/adex/types.d.ts @@ -0,0 +1,4 @@ +interface ImportMeta { + readonly server: boolean + readonly client: boolean +} diff --git a/packages/adapters/node/lib/index.js b/packages/adapters/node/lib/index.js index 61f51ae..ecdd155 100644 --- a/packages/adapters/node/lib/index.js +++ b/packages/adapters/node/lib/index.js @@ -1,6 +1,5 @@ import { existsSync } from 'node:fs' import http from 'node:http' - import { sirv, useMiddleware } from 'adex/ssr' import { handler } from 'virtual:adex:handler' diff --git a/playground/.islands/island-counter.js b/playground/.islands/island-counter.js index 0082e0c..8607481 100644 --- a/playground/.islands/island-counter.js +++ b/playground/.islands/island-counter.js @@ -1,7 +1,6 @@ +import { render, h } from 'preact' - import { render, h } from 'preact'; - - // h() imports will be added at build time since this file is inlined with the client template +// h() imports will be added at build time since this file is inlined with the client template const restoreTree = (type, props = {}) => { if (typeof props.children === 'object') { @@ -61,49 +60,53 @@ function mergePropsWithDOM(rootNode, props) { rootNode.append(...scriptNodes) } +init() - init() - - function init(){ - if(customElements.get("island-counter")){ - return - } - customElements.define("island-counter", class IslandCounter extends HTMLElement { - constructor(){ - super(); +function init() { + if (customElements.get('island-counter')) { + return + } + customElements.define( + 'island-counter', + class IslandCounter extends HTMLElement { + constructor() { + super() } - + async connectedCallback() { - const c = await import("/Users/sid/code/adex/playground/src/components/counter.tsx"); - const usableComponent = c["Counter"] - const props = JSON.parse(this.dataset.props || '{}'); - this.baseProps = props - this.component = usableComponent - this.renderOnView({threshold:0.2}) + const c = await import( + '/Users/sid/code/adex/playground/src/components/counter.tsx' + ) + const usableComponent = c['Counter'] + const props = JSON.parse(this.dataset.props || '{}') + this.baseProps = props + this.component = usableComponent + this.renderOnView({ threshold: 0.2 }) } - - renderOnView({threshold} = {}){ + + renderOnView({ threshold } = {}) { const options = { root: null, threshold, - }; - - const self = this; - - const callback = function(entries, observer) { - entries.forEach((entry) => { - if(!entry.isIntersecting) return + } + + const self = this + + const callback = function (entries, observer) { + entries.forEach(entry => { + if (!entry.isIntersecting) return self.renderIsland() - }); + }) } - - let observer = new IntersectionObserver(callback, options); - observer.observe(this); + + let observer = new IntersectionObserver(callback, options) + observer.observe(this) } - - renderIsland(){ - mergePropsWithDOM(this, this.baseProps); + + renderIsland() { + mergePropsWithDOM(this, this.baseProps) render(restoreTree(this.component, this.baseProps), this, undefined) } - }) - } \ No newline at end of file + } + ) +} diff --git a/playground/src/components/SharedSignal.tsx b/playground/src/components/SharedSignal.tsx index 115dc14..4024336 100644 --- a/playground/src/components/SharedSignal.tsx +++ b/playground/src/components/SharedSignal.tsx @@ -15,7 +15,12 @@ export function Triggerer() { return (