-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(fix): decorate exports use parse-advanced, fixes #36
- Loading branch information
Showing
9 changed files
with
538 additions
and
630 deletions.
There are no files selected for viewing
113 changes: 113 additions & 0 deletions
113
packages/vinxi-directives/fixtures/decorate-exports.snapshot.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { createReference } from '~/runtime'; | ||
|
||
import { cache } from "@solidjs/router"; | ||
import { consola } from "consola"; | ||
import { createStorage } from "unstorage"; | ||
import redisDriver from "unstorage/drivers/redis"; | ||
import type { MenuItem, Settings } from "~/types/common"; | ||
|
||
const debugFetcher = import.meta.env.VITE_COG_DEBUG_FETCHER == 1; | ||
const cacheEnabled = import.meta.env.VITE_COG_CACHE_ENABLED == 1; | ||
const cacheDebug = import.meta.env.VITE_COG_CACHE_DEBUG == 1; | ||
|
||
const storage = createStorage({ | ||
driver: redisDriver({ | ||
base: import.meta.env.VITE_KB_REDIS_BASE ?? "cog:", | ||
host: import.meta.env.VITE_KB_REDIS_HOST ?? "localhost", | ||
port: import.meta.env.VITE_KB_REDIS_PORT ?? 6379, | ||
}), | ||
}); | ||
|
||
async function fetchAPI(path: string) { | ||
const headers = new Headers(); | ||
headers.append("User-Agent", "chrome"); | ||
|
||
if (import.meta.env.VITE_COG_BACKEND_AUTH_LOGIN.length > 0) { | ||
headers.append( | ||
"Authorization", | ||
"Basic " + | ||
btoa( | ||
import.meta.env.VITE_KB_BACKEND_AUTH_LOGIN + | ||
":" + | ||
import.meta.env.VITE_KB_BACKEND_AUTH_PASSWORD, | ||
), | ||
); | ||
} | ||
|
||
let url = `${import.meta.env.VITE_COG_BACKEND_BASE_URL}/${path}/`; | ||
|
||
if (url.indexOf("?") > -1) { | ||
// remove trailing slash | ||
url = url.replace(/\/$/, ""); | ||
} | ||
|
||
try { | ||
debugFetcher && consola.info(`Fetching ${url}`); | ||
const response = await fetch(url, { headers }); | ||
|
||
// @TODO we should probably cache error responses for a short period of time | ||
if ("error" in response) { | ||
return response; | ||
} | ||
|
||
if (response.status !== 200) { | ||
// @TODO we should probably cache error responses for a short period of time | ||
return { | ||
code: response.status, | ||
error: `Server responded with status ${response.status}`, | ||
}; | ||
} | ||
|
||
const json = await response.json().catch((error: string): void => { | ||
throw new Error(`Was Fetching ${url}, got ${error}`); | ||
}); | ||
|
||
if (!("data" in json)) { | ||
return { | ||
code: 500, | ||
error: `JSON response does not contain data`, | ||
}; | ||
} | ||
|
||
if (cacheEnabled) { | ||
await storage.setItem(path, json.data); | ||
} | ||
|
||
return json.data; | ||
} catch (error) { | ||
return { error }; | ||
} | ||
} | ||
|
||
export const getDataAtPath = async (path: string) => { | ||
"use runtime"; | ||
|
||
if (cacheEnabled) { | ||
const data = await storage.getItem(path); | ||
if (data) { | ||
cacheDebug && consola.info(`Cache hit: ${path.slice(0, 80)}…`); | ||
return data; | ||
} | ||
} | ||
|
||
cacheDebug && consola.info(`Cache miss: ${path.slice(0, 80)}…`); | ||
return fetchAPI(path); | ||
}; | ||
|
||
export const getSettings = cache(async (): Promise<Settings> => { | ||
return getDataAtPath("solid/settings"); | ||
}, "settings"); | ||
|
||
export const getMenuMain = cache(async (): Promise<MenuItem[]> => { | ||
return getDataAtPath("solid/menu/main"); | ||
}, "menu:main"); | ||
|
||
export const getMenuFooter = cache(async (): Promise<MenuItem[]> => { | ||
return getDataAtPath("solid/menu/footer"); | ||
}, "menu:footer"); | ||
|
||
|
||
;if (typeof getDataAtPath === "function") createReference(getDataAtPath,"test", "getDataAtPath"); | ||
if (typeof getSettings === "function") createReference(getSettings,"test", "getSettings"); | ||
if (typeof getMenuMain === "function") createReference(getMenuMain,"test", "getMenuMain"); | ||
if (typeof getMenuFooter === "function") createReference(getMenuFooter,"test", "getMenuFooter"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
"use runtime"; | ||
|
||
import { cache } from "@solidjs/router"; | ||
import { consola } from "consola"; | ||
import { createStorage } from "unstorage"; | ||
import redisDriver from "unstorage/drivers/redis"; | ||
import type { MenuItem, Settings } from "~/types/common"; | ||
|
||
const debugFetcher = import.meta.env.VITE_COG_DEBUG_FETCHER == 1; | ||
const cacheEnabled = import.meta.env.VITE_COG_CACHE_ENABLED == 1; | ||
const cacheDebug = import.meta.env.VITE_COG_CACHE_DEBUG == 1; | ||
|
||
const storage = createStorage({ | ||
driver: redisDriver({ | ||
base: import.meta.env.VITE_KB_REDIS_BASE ?? "cog:", | ||
host: import.meta.env.VITE_KB_REDIS_HOST ?? "localhost", | ||
port: import.meta.env.VITE_KB_REDIS_PORT ?? 6379, | ||
}), | ||
}); | ||
|
||
async function fetchAPI(path: string) { | ||
const headers = new Headers(); | ||
headers.append("User-Agent", "chrome"); | ||
|
||
if (import.meta.env.VITE_COG_BACKEND_AUTH_LOGIN.length > 0) { | ||
headers.append( | ||
"Authorization", | ||
"Basic " + | ||
btoa( | ||
import.meta.env.VITE_KB_BACKEND_AUTH_LOGIN + | ||
":" + | ||
import.meta.env.VITE_KB_BACKEND_AUTH_PASSWORD, | ||
), | ||
); | ||
} | ||
|
||
let url = `${import.meta.env.VITE_COG_BACKEND_BASE_URL}/${path}/`; | ||
|
||
if (url.indexOf("?") > -1) { | ||
// remove trailing slash | ||
url = url.replace(/\/$/, ""); | ||
} | ||
|
||
try { | ||
debugFetcher && consola.info(`Fetching ${url}`); | ||
const response = await fetch(url, { headers }); | ||
|
||
// @TODO we should probably cache error responses for a short period of time | ||
if ("error" in response) { | ||
return response; | ||
} | ||
|
||
if (response.status !== 200) { | ||
// @TODO we should probably cache error responses for a short period of time | ||
return { | ||
code: response.status, | ||
error: `Server responded with status ${response.status}`, | ||
}; | ||
} | ||
|
||
const json = await response.json().catch((error: string): void => { | ||
throw new Error(`Was Fetching ${url}, got ${error}`); | ||
}); | ||
|
||
if (!("data" in json)) { | ||
return { | ||
code: 500, | ||
error: `JSON response does not contain data`, | ||
}; | ||
} | ||
|
||
if (cacheEnabled) { | ||
await storage.setItem(path, json.data); | ||
} | ||
|
||
return json.data; | ||
} catch (error) { | ||
return { error }; | ||
} | ||
} | ||
|
||
export const getDataAtPath = async (path: string) => { | ||
"use runtime"; | ||
|
||
if (cacheEnabled) { | ||
const data = await storage.getItem(path); | ||
if (data) { | ||
cacheDebug && consola.info(`Cache hit: ${path.slice(0, 80)}…`); | ||
return data; | ||
} | ||
} | ||
|
||
cacheDebug && consola.info(`Cache miss: ${path.slice(0, 80)}…`); | ||
return fetchAPI(path); | ||
}; | ||
|
||
export const getSettings = cache(async (): Promise<Settings> => { | ||
return getDataAtPath("solid/settings"); | ||
}, "settings"); | ||
|
||
export const getMenuMain = cache(async (): Promise<MenuItem[]> => { | ||
return getDataAtPath("solid/menu/main"); | ||
}, "menu:main"); | ||
|
||
export const getMenuFooter = cache(async (): Promise<MenuItem[]> => { | ||
return getDataAtPath("solid/menu/footer"); | ||
}, "menu:footer"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,7 @@ | ||
export { directives } from "./plugin.js"; | ||
export { decorateExportsPlugin } from "./transform.js"; | ||
export { wrapExports, wrapExportsPlugin } from "./wrap-exports.js"; | ||
export { shimExports, shimExportsPlugin } from "./shim-exports.js"; | ||
export { | ||
decorateExportsPlugin, | ||
decorateExports, | ||
} from "./plugins/decorate-exports.js"; | ||
export { wrapExports, wrapExportsPlugin } from "./plugins/wrap-exports.js"; | ||
export { shimExports, shimExportsPlugin } from "./plugins/shim-exports.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { print, types } from "recast"; | ||
|
||
import { parseAdvanced } from "../parse.js"; | ||
import { addLocalExportedNames } from "../utils.js"; | ||
|
||
export function decorateExports({ | ||
code, | ||
id, | ||
ast, | ||
runtime, | ||
hash, | ||
options, | ||
directive, | ||
}) { | ||
// onServerReference(moduleId); | ||
// If the same local name is exported more than once, we only need one of the names. | ||
const localNames = new Map(); | ||
const localTypes = new Map(); | ||
|
||
for (let i = 0; i < ast.program.body.length; i++) { | ||
const node = ast.program.body[i]; | ||
switch (node.type) { | ||
case "ExportAllDeclaration": | ||
// If export * is used, the other file needs to explicitly opt into "use server" too. | ||
break; | ||
case "ExportDefaultDeclaration": | ||
if (node.declaration.type === "Identifier") { | ||
localNames.set(node.declaration.name, "default"); | ||
} else if (node.declaration.type === "FunctionDeclaration") { | ||
if (node.declaration.id) { | ||
localNames.set(node.declaration.id.name, "default"); | ||
localTypes.set(node.declaration.id.name, "function"); | ||
} else { | ||
// TODO: This needs to be rewritten inline because it doesn't have a local name. | ||
} | ||
} | ||
continue; | ||
case "ExportNamedDeclaration": | ||
if (node.declaration) { | ||
if (node.declaration.type === "VariableDeclaration") { | ||
const declarations = node.declaration.declarations; | ||
for (let j = 0; j < declarations.length; j++) { | ||
addLocalExportedNames(localNames, declarations[j].id); | ||
} | ||
} else { | ||
const name = node.declaration.id.name; | ||
localNames.set(name, name); | ||
if (node.declaration.type === "FunctionDeclaration") { | ||
localTypes.set(name, "function"); | ||
} | ||
} | ||
} | ||
if (node.specifiers) { | ||
const specifiers = node.specifiers; | ||
for (let j = 0; j < specifiers.length; j++) { | ||
const specifier = specifiers[j]; | ||
localNames.set(specifier.local.name, specifier.exported.name); | ||
} | ||
} | ||
continue; | ||
} | ||
} | ||
|
||
ast.program.body = [ | ||
types.builders.importDeclaration( | ||
[ | ||
types.builders.importSpecifier( | ||
types.builders.identifier(runtime.function), | ||
), | ||
], | ||
types.builders.stringLiteral(runtime.module), | ||
), | ||
...ast.program.body.filter((node) => node.directive !== directive), | ||
...[...localNames.entries()].map(([local, exported]) => { | ||
// return an if block that checks if the export is a function and if so annotates it. | ||
return types.builders.ifStatement( | ||
types.builders.binaryExpression( | ||
"===", | ||
types.builders.unaryExpression( | ||
"typeof", | ||
types.builders.identifier(local), | ||
), | ||
types.builders.stringLiteral("function"), | ||
), | ||
types.builders.expressionStatement( | ||
types.builders.callExpression( | ||
types.builders.identifier(runtime.function), | ||
[ | ||
types.builders.identifier(local), | ||
types.builders.stringLiteral( | ||
options.command === "build" ? hash(id) : id, | ||
), | ||
types.builders.stringLiteral(exported), | ||
], | ||
), | ||
), | ||
); | ||
}), | ||
]; | ||
|
||
ast.program.directives = ast.program.directives?.filter( | ||
(node) => node.value !== directive, | ||
); | ||
|
||
let newSrc = print(ast).code; | ||
return newSrc; | ||
} | ||
|
||
export function decorateExportsPlugin({ | ||
runtime, | ||
hash, | ||
pragma, | ||
apply, | ||
onModuleFound, | ||
}) { | ||
return { | ||
name: "decorate-exports", | ||
async transform(code, id, options, ctx) { | ||
if (code.indexOf(pragma) === -1) { | ||
return code; | ||
} | ||
|
||
const shouldApply = apply(code, id, options); | ||
|
||
if (!shouldApply) { | ||
return code; | ||
} | ||
|
||
function hasDir(node) { | ||
return node?.directives?.[0]?.value?.value === pragma; | ||
} | ||
|
||
function hasFunctionDirective(node) { | ||
return hasDir(node.body); | ||
} | ||
|
||
const ast = parseAdvanced(code); | ||
|
||
if (ast.length === 0) { | ||
return code; | ||
} | ||
|
||
if (hasDir(ast.program)) { | ||
ast.program.directives = []; | ||
let result = await decorateExports({ | ||
runtime, | ||
ast, | ||
id, | ||
code, | ||
hash, | ||
options, | ||
directive: pragma, | ||
}); | ||
onModuleFound?.(id); | ||
return result; | ||
} | ||
|
||
return code; | ||
}, | ||
}; | ||
} |
Oops, something went wrong.