Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Astro 5 #207

Merged
merged 8 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: 20.x

- name: Ubuntu AppArmor fix
if: ${{ matrix.os == 'ubuntu-latest' }}
# Ubuntu >= 23 has AppArmor enabled by default, which breaks Puppeteer.
# See https://github.com/puppeteer/puppeteer/issues/12818 "No usable sandbox!"
# this is taken from the solution used in Puppeteer's own CI: https://github.com/puppeteer/puppeteer/pull/13196
# The alternative is to pin Ubuntu 22 or to use aa-exec to disable AppArmor for commands that need Puppeteer.
# This is also suggested by Chromium https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
run: |
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
shell: bash
# TODO: Remove when possible (https://github.com/actions/setup-node/issues/515)
- name: Windows Node fix
if: ${{ matrix.os == 'windows-latest' }}
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: 20.x
- name: Ubuntu AppArmor fix
if: ${{ matrix.os == 'ubuntu-latest' }}
# Ubuntu >= 23 has AppArmor enabled by default, which breaks Puppeteer.
# See https://github.com/puppeteer/puppeteer/issues/12818 "No usable sandbox!"
# this is taken from the solution used in Puppeteer's own CI: https://github.com/puppeteer/puppeteer/pull/13196
# The alternative is to pin Ubuntu 22 or to use aa-exec to disable AppArmor for commands that need Puppeteer.
# This is also suggested by Chromium https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md
run: |
echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
shell: bash
# TODO: Remove when possible (https://github.com/actions/setup-node/issues/515)
- name: Windows Node fix
if: ${{ matrix.os == 'windows-latest' }}
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

## Unreleased

* Added support for Astro 5.
* Added support for `astro:env` and public environment variables in Astro components.
* Added support for the `getImage` function in Astro components.
* Added fallbacks for the `astro:actions`, `astro:i18n`, `astro middleware`, and `astro:transitions` virtual modules.

## v3.12.0 (October 28, 2024)

* Added a `--disable-bindings` flag to the `@bookshop/generate` command.
Expand Down
70 changes: 52 additions & 18 deletions javascript-modules/engines/astro-engine/lib/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ const { transform: bookshopTransform } = AstroPluginVite({
__removeClientDirectives: true,
});

const getEnvironmentDefines = () => {
return Object.entries(process.env ?? {}).reduce((acc, [key, value]) => {
if(key.startsWith("PUBLIC_")){
acc[`import.meta.env.${key}`] = JSON.stringify(value);
}
return acc;
}, {})
}

export const buildPlugins = [
sassPlugin({
filter: /\.module\.(s[ac]ss|css)$/,
Expand Down Expand Up @@ -83,7 +92,13 @@ export const buildPlugins = [

build.onResolve({ filter: /^astro:.*$/ }, async (args) => {
const type = args.path.replace("astro:", "");
if (type !== "content" && type !== "assets") {
if (type === "env/client" || type === "env"){
return { path: 'env', namespace: 'virtual' };
}
if (type === "transitions/client" || type === "transitions"){
return { path: join(dir, "modules", "transitions.js").replace("file:", "") };
}
if (!["content", "assets", "i18n", "actions", "middleware"].includes(type)) {
console.error(
`Error: The 'astro:${type}' module is not supported inside Bookshop components.`
);
Expand Down Expand Up @@ -118,6 +133,35 @@ export const buildPlugins = [
}
});

build.onLoad({ filter: /^env$/, namespace: 'virtual' }, async (args) => {
let contents = "";
Object.entries(astroConfig?.env?.schema ?? {}).forEach(([key, schema]) => {
if(schema.context !== "client" || schema.access !== "public"){
return;
}

try{
switch(schema.type){
case "boolean":
contents += `export const ${key} = ${!!process.env[key]};\n`
break;
case "number":
contents += `export const ${key} = ${Number(process.env[key])};\n`
break;
default:
contents += `export const ${key} = ${JSON.stringify(process.env[key] ?? "")};\n`
}
} catch(e){
//Error intentionally ignored
}
});
contents += "export const getSecret = () => console.warn(\"[Bookshop] getSecret is not supported in Bookshop. Please use an editing fallback instead.\");"
return {
contents,
loader: "js",
};
});

build.onLoad({ filter: /\.astro$/, namespace: "style" }, async (args) => {
let text = await fs.promises.readFile(args.path, "utf8");
let transformed = await transform(text, {
Expand Down Expand Up @@ -155,9 +199,9 @@ export const buildPlugins = [
loader: "ts",
target: "esnext",
sourcemap: false,
define: { ENV_BOOKSHOP_LIVE: "true" },
define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true"},
});
let result = await bookshopTransform(
let result = bookshopTransform(
jsResult.code,
args.path.replace(process.cwd(), "")
);
Expand Down Expand Up @@ -189,10 +233,10 @@ export const buildPlugins = [
jsxFragment: "__React.Fragment",
target: "esnext",
sourcemap: false,
define: { ENV_BOOKSHOP_LIVE: "true" },
define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true" },
});

let result = await bookshopTransform(
let result = bookshopTransform(
jsResult.code,
args.path.replace(process.cwd(), "")
);
Expand All @@ -218,26 +262,16 @@ export const buildPlugins = [
loader: "ts",
target: "esnext",
sourcemap: false,
define: { ENV_BOOKSHOP_LIVE: "true" },
define: { ...getEnvironmentDefines(), ENV_BOOKSHOP_LIVE: "true" },
});

let result = await bookshopTransform(
jsResult.code,
args.path.replace(process.cwd(), "")
);

if (!result) {
console.warn("Bookshop transform failed:", args.path);
result = jsResult;
}

let importTransform = (await resolveConfig({}, "build")).plugins.find(
({ name }) => name === "vite:import-glob"
).transform;
let viteResult = await importTransform(result.code, args.path);
let viteResult = await importTransform(jsResult.code, args.path);

return {
contents: viteResult?.code ?? result.code,
contents: viteResult?.code ?? jsResult.code,
loader: "js",
};
});
Expand Down
23 changes: 22 additions & 1 deletion javascript-modules/engines/astro-engine/lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class Engine {
this.reactRoots.push({Component, props});
return { html: `<div data-react-root=${this.reactRoots.length-1}></div>` };
}

const reactNode = await Component(props);
return { html: renderToStaticMarkup(reactNode) };
},
Expand Down Expand Up @@ -118,12 +118,30 @@ export class Engine {

async renderAstroComponent(target, key, props, globals) {
const component = this.files?.[key];

let encryptionKey;
try{
encryptionKey = window.crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"],
)
} catch(err){
console.warn("[Bookshop] Could not generate a key for Astro component. This may cause issues with Astro components that use server-islands")
}

const SSRResult = {
styles: new Set(),
scripts: new Set(),
links: new Set(),
propagation: new Map(),
propagators: new Map(),
serverIslandNameMap: { get: () => "Bookshop" },
key: encryptionKey,
base: "/",
extraHead: [],
componentMetadata: new Map(),
renderers: this.renderers,
Expand Down Expand Up @@ -166,6 +184,9 @@ export class Engine {
flushSync(() => root.render(reactNode));
});
this.reactRoots = [];
target.querySelectorAll("link, [data-island-id]").forEach((node) => {
node.remove();
});
}

async eval(str, props = [{}]) {
Expand Down
51 changes: 51 additions & 0 deletions javascript-modules/engines/astro-engine/lib/modules/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export const actions = new Proxy({}, {
get() {
console.warn("[Bookshop] actions is not supported in Bookshop. Please use an editing fallback instead.");
return () => {};
}
});

export const defineAction = () => {
console.warn("[Bookshop] defineAction is not supported in Bookshop. Please use an editing fallback instead.");
return {
handler: () => {},
input: null
};
};

export const isInputError = (error) => {
console.warn("[Bookshop] isInputError is not supported in Bookshop. Please use an editing fallback instead.");
return false;
};

export const isActionError = (error) => {
console.warn("[Bookshop] isActionError is not supported in Bookshop. Please use an editing fallback instead.");
return false;
};

export class ActionError extends Error {
constructor(code, message) {
super(message);
console.warn("[Bookshop] ActionError is not supported in Bookshop. Please use an editing fallback instead.");
this.code = code;
}
}

export const getActionContext = (context) => {
console.warn("[Bookshop] getActionContext is not supported in Bookshop. Please use an editing fallback instead.");
return {
action: undefined,
setActionResult: () => {},
serializeActionResult: () => ({})
};
};

export const deserializeActionResult = (result) => {
console.warn("[Bookshop] deserializeActionResult is not supported in Bookshop. Please use an editing fallback instead.");
return {};
};

export const getActionPath = (action) => {
console.warn("[Bookshop] getActionPath is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};
27 changes: 27 additions & 0 deletions javascript-modules/engines/astro-engine/lib/modules/assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,30 @@ import PictureInternal from './picture.astro';

export const Image = ImageInternal;
export const Picture = PictureInternal;

export const getImage = async (options) => {
const resolvedSrc =
typeof options.src === "object" && "then" in options.src
? (await options.src).default ?? (await options.src)
: options.src;
return {
rawOptions: {
src: {
src: resolvedSrc,
},
},
options: {
src: {
src: resolvedSrc,
},
},
src: resolvedSrc,
srcSet: { values: [] },
attributes: { }
}
}

export const inferRemoteSize = async () => {
console.warn("[Bookshop] inferRemoteSize is not supported in Bookshop. Please use an editing fallback instead.");
return {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
console.warn("[Bookshop] view transitions are not supported in Bookshop. Please use an editing fallback instead.");
---
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ export const getEntries = (entries) => {
export const getEntryBySlug = (collection, slug) => {
return getEntry({ collection, slug });
};

export const render = async () => ({ Content: () => "Content is not available when live editing", headings: [], remarkPluginFrontmatter: {} });

export const defineCollection = () => console.warn("[Bookshop] defineCollection is not supported in Bookshop. Make sure you're not importing your config in a component file by mistake.");
export const reference = () => console.warn("[Bookshop] reference is not supported in Bookshop. Make sure you're not importing your config in a component file by mistake.");
54 changes: 54 additions & 0 deletions javascript-modules/engines/astro-engine/lib/modules/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export const getRelativeLocaleUrl = (locale, path, options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};

export const getAbsoluteLocaleUrl = (locale, path, options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};

export const getRelativeLocaleUrlList = (path, options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return [];
};

export const getAbsoluteLocaleUrlList = (path, options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return [];
};

export const getPathByLocale = (locale) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};

export const getLocaleByPath = (path) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};

export const redirectToDefaultLocale = (context, statusCode) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return Promise.resolve(new Response());
};

export const redirectToFallback = (context, response) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return Promise.resolve(new Response());
};

export const notFound = (context, response) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return Promise.resolve(new Response());
};

export const middleware = (options) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return () => {};
};

export const requestHasLocale = (context) => {
console.warn("[Bookshop] i18n routing is not supported in Bookshop. Please use an editing fallback instead.");
return false;
};
19 changes: 19 additions & 0 deletions javascript-modules/engines/astro-engine/lib/modules/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const sequence = (...handlers) => {
console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
return () => {};
};

export const defineMiddleware = (fn) => {
console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
return () => {};
};

export const createContext = (context) => {
console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
return {};
};

export const trySerializeLocals = (value) => {
console.warn("[Bookshop] middleware is not supported in Bookshop. Please use an editing fallback instead.");
return '';
};
Loading
Loading