@altano/astro-prettier-response 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # astro-prettier-response
2
+
3
+ This package injects a middleware that runs the [Prettier formatter](https://prettier.io) for all output to make it unminified and pretty ✨. If it can't format something, it returns it as-is.
4
+
5
+ ## Wait, what?
6
+
7
+ This defies standard convention: instead producing minified output that is _as small as possible_, this makes your output _as pretty as possible_. You probably don't want to use this integration, and if you think you do, you almost certainly only want to use it on your statically-generated content.
8
+
9
+ See [https://alan.norbauer.com/articles/dying-for-beauty/](https://alan.norbauer.com/articles/dying-for-beauty/) for a write-up of why I made this.
10
+
11
+ # Prerequisites
12
+
13
+ This integration is for [Astro](https://astro.build). If you aren't using Astro you're in the wrong place.
14
+
15
+ # Installation
16
+
17
+ ## Astro Integration Installer
18
+
19
+ In your existing Astro project:
20
+
21
+ ```sh
22
+ # Using NPM
23
+ npx astro add @altano/astro-prettier-response
24
+ # Using Yarn
25
+ yarn astro add @altano/astro-prettier-response
26
+ # Using PNPM
27
+ pnpm astro add @altano/astro-prettier-response
28
+ ```
29
+
30
+ ## Manual Installation
31
+
32
+ Install the package:
33
+
34
+ ```sh
35
+ # Using NPM
36
+ npx install @altano/astro-prettier-response
37
+ # Using Yarn
38
+ yarn add @altano/astro-prettier-response
39
+ # Using PNPM
40
+ pnpm add @altano/astro-prettier-response
41
+ ```
42
+
43
+ Add it to your Astro config:
44
+
45
+ ```js
46
+ import prettierResponse from "@altano/astro-prettier-response";
47
+
48
+ export default defineConfig({
49
+ integrations: [prettierResponse()],
50
+ });
51
+ ```
52
+
53
+ ## Configuration Options
54
+
55
+ The integration works fine with no manual configuration, but here are the options and their default values:
56
+
57
+ ```js
58
+ import prettierResponse from "@altano/astro-prettier-response";
59
+
60
+ export default defineConfig({
61
+ integrations: [
62
+ prettierResponse({
63
+ disableMinifiers: true, // disable Astro's html/css/js minifiers
64
+ formatXml: false, // requires the `@prettier/plugin-xml` package be installed
65
+ }),
66
+ ],
67
+ });
68
+ ```
69
+
70
+ ## Whitespace Sensitivity
71
+
72
+ Prettier is sensitive to significant whitespace in its formatting. This integration leaves the default `--html-whitespace-sensitivity=css` in place. If you run into trouble you can override this default setting on a case-by-case basis right in your html and it will be respected by Prettier. See https://prettier.io/blog/2018/11/07/1.15.0.html#whitespace-sensitive-formatting for more information about html whitespace handling and https://github.com/prettier/plugin-xml?tab=readme-ov-file#whitespace for xml whitespace handling.
73
+
74
+ ## Config Overrides
75
+
76
+ You might have noticed this line in your Astro logs:
77
+
78
+ > [astro-prettier-response] Disabling minification of html/css/js in Astro config
79
+
80
+ This is warning you that this integration is overriding your config to disable Astro's html/js/css minification, because that's obviously what you want.
81
+
82
+ If it's _not_ what you want, and you want Pretty-but-minified output for some reason, you can configure the integration to respect your Astro settings without overriding them:
83
+
84
+ ```js
85
+ import prettierResponse from "@altano/astro-prettier-response";
86
+
87
+ export default defineConfig({
88
+ integrations: [
89
+ prettierResponse({
90
+ // Leave Astro's minifiers on (sorry for the double-negative it is what it is)
91
+ // ⚠️ NOT RECOMMENDED
92
+ disableMinifiers: false,
93
+ }),
94
+ ],
95
+ });
96
+ ```
97
+
98
+ And if you like the behavior but you just hate seeing this logged every time you build, then configure Astro to disable minification yourself and the integration will stop logging:
99
+
100
+ ```js
101
+ import prettierResponse from "@altano/astro-prettier-response";
102
+
103
+ export default defineConfig({
104
+ integrations: [prettierResponse()],
105
+ vite: {
106
+ build: {
107
+ minify: false,
108
+ cssMinify: false,
109
+ },
110
+ },
111
+ compressHTML: false,
112
+ });
113
+ ```
114
+
115
+ This will leave minification enabled but will still run your output through Prettier.
116
+
117
+ ## How This Integration Works
118
+
119
+ This integration injects a middleware that intercepts all requests. It looks at the `Content-Type` of every `Response` and determines the extension (using the `mime-types` npm package). This is passed along to Prettier which uses it to determine the appropriate parser to use, and then it attempts formatting.
120
+
121
+ If your Response doesn't have a `Content-Type`, it won't be formatted, so make sure you return one in the headers of your endpoints.
122
+
123
+ Formatting is only attempted if the `Response.status` is 2xx or 4xx (so a 500 error, for example, will pass through the middleware untouched).
124
+
125
+ ## Troubleshooting
126
+
127
+ If you are getting back unformatted content and you don't know why:
128
+
129
+ - If it's an endpoint, make sure you return a `Content-Type` header (.astro files have this set automatically).
130
+ - If it's an xml file, make sure you've enabled the `formatXml` option and installed the `@prettier/plugin-xml` package.
131
+ - Try enabling Astro's verbose logging to get more log output from this integration, e.g. `astro build --verbose`, which would give you output about whether each resource was successfully formatted or not:
132
+ ![snippet from the build logs of running the `astro build --verbose` command](./readme/verbose-logs.png)
133
+
134
+ If you are getting back formatted content, but it doesn't look like what you would expect, consult [the section above on whitespace sensitivity](#whitespace-sensitivity).
135
+
136
+ ## Special Thanks
137
+
138
+ Thank you to [the Astro team](https://github.com/withastro/astro/graphs/contributors) for creating such a great site generator, especially [Fryuni](https://fryuni.dev/) for creating [Inox Tools](https://inox-tools.fryuni.dev/) which power all my Astro integrations, including this one. It makes them easier to write and possible to test and I'm not sure I'd want to make Astro integrations without them.
@@ -0,0 +1,38 @@
1
+ import { z } from "astro/zod";
2
+ export declare const ConfigSchema: z.ZodDefault<z.ZodOptional<z.ZodObject<{
3
+ /**
4
+ * Astro/Vite will minify your HTML/JS/CSS by default. Astro lightly
5
+ * compresses your HTML (not a full minifier) while Vite will fully compress
6
+ * your CSS/JS. Since we're going for prettiest output here, it makes sense
7
+ * for this integration to fully disable such minification.
8
+ *
9
+ * If you set this to false, and you use the default Astro config with
10
+ * minification enabled, you will still get your output formatted by
11
+ * Prettier, but it will be minified output that is formatted. So, for
12
+ * example, most of the variable names in your JS will be one character
13
+ * letters.
14
+ *
15
+ * @defaultValue true
16
+ */
17
+ disableMinifiers: z.ZodDefault<z.ZodBoolean>;
18
+ /**
19
+ * Install `@prettier/plugin-xml` package if you enable this
20
+ *
21
+ * Use the official Prettier plugin to format xml as well. Sitemaps and rss
22
+ * feeds are xml, so this would probably do something on your stock Astro
23
+ * site. Note: the Prettier output is only marginally better because xml is
24
+ * very whitespace sensitive and so Prettier's formatting is very strict and
25
+ * not that much prettier.
26
+ *
27
+ * @defaultValue false
28
+ */
29
+ formatXml: z.ZodDefault<z.ZodBoolean>;
30
+ }, "strict", z.ZodTypeAny, {
31
+ disableMinifiers: boolean;
32
+ formatXml: boolean;
33
+ }, {
34
+ disableMinifiers?: boolean | undefined;
35
+ formatXml?: boolean | undefined;
36
+ }>>>;
37
+ export type AstroPrettierResponseConfig = z.infer<typeof ConfigSchema>;
38
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { default } from "./integration.js";
2
+ export type { AstroPrettierResponseConfig } from "./config.js";
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ // src/integration.ts
2
+ import { runtimeLogger } from "@inox-tools/runtime-logger";
3
+ import {
4
+ addVirtualImports,
5
+ createResolver,
6
+ defineIntegration
7
+ } from "astro-integration-kit";
8
+
9
+ // src/config.ts
10
+ import { z } from "astro/zod";
11
+ var ConfigSchema = z.object({
12
+ /**
13
+ * Astro/Vite will minify your HTML/JS/CSS by default. Astro lightly
14
+ * compresses your HTML (not a full minifier) while Vite will fully compress
15
+ * your CSS/JS. Since we're going for prettiest output here, it makes sense
16
+ * for this integration to fully disable such minification.
17
+ *
18
+ * If you set this to false, and you use the default Astro config with
19
+ * minification enabled, you will still get your output formatted by
20
+ * Prettier, but it will be minified output that is formatted. So, for
21
+ * example, most of the variable names in your JS will be one character
22
+ * letters.
23
+ *
24
+ * @defaultValue true
25
+ */
26
+ disableMinifiers: z.boolean().default(true),
27
+ /**
28
+ * Install `@prettier/plugin-xml` package if you enable this
29
+ *
30
+ * Use the official Prettier plugin to format xml as well. Sitemaps and rss
31
+ * feeds are xml, so this would probably do something on your stock Astro
32
+ * site. Note: the Prettier output is only marginally better because xml is
33
+ * very whitespace sensitive and so Prettier's formatting is very strict and
34
+ * not that much prettier.
35
+ *
36
+ * @defaultValue false
37
+ */
38
+ formatXml: z.boolean().default(false)
39
+ }).strict().optional().default({});
40
+
41
+ // src/integration.ts
42
+ var integration_default = defineIntegration({
43
+ name: "astro-prettier-response",
44
+ optionsSchema: ConfigSchema,
45
+ setup({ options, name }) {
46
+ const { resolve } = createResolver(import.meta.url);
47
+ return {
48
+ hooks: {
49
+ "astro:config:setup": (params) => {
50
+ addVirtualImports(params, {
51
+ name,
52
+ imports: {
53
+ "virtual:astro-prettier-response/config": `export default ${JSON.stringify(ConfigSchema.parse(options))}`
54
+ }
55
+ });
56
+ runtimeLogger(params, { name: "astro-prettier-response" });
57
+ const { config, updateConfig, addMiddleware, logger } = params;
58
+ if (options.disableMinifiers) {
59
+ let didConfigOverride = false;
60
+ if (config.compressHTML !== false) {
61
+ didConfigOverride = true;
62
+ updateConfig({
63
+ compressHTML: false
64
+ });
65
+ }
66
+ if (config?.vite?.build?.minify !== false) {
67
+ didConfigOverride = true;
68
+ updateConfig({
69
+ vite: {
70
+ build: {
71
+ minify: false
72
+ }
73
+ }
74
+ });
75
+ }
76
+ if (config?.vite?.build?.cssMinify !== false) {
77
+ didConfigOverride = true;
78
+ updateConfig({
79
+ vite: {
80
+ build: {
81
+ cssMinify: false
82
+ }
83
+ }
84
+ });
85
+ }
86
+ if (didConfigOverride) {
87
+ logger.info(
88
+ `Disabling minification of html/css/js in Astro config (https://github.com/altano/npm-packages/tree/main/packages/astro-prettier-response#config-overrides)`
89
+ );
90
+ }
91
+ }
92
+ addMiddleware({
93
+ entrypoint: resolve("./middleware/index.ts"),
94
+ order: "post"
95
+ });
96
+ }
97
+ }
98
+ };
99
+ }
100
+ });
101
+ export {
102
+ integration_default as default
103
+ };
@@ -0,0 +1,6 @@
1
+ declare const _default: (options?: {
2
+ disableMinifiers?: boolean | undefined;
3
+ formatXml?: boolean | undefined;
4
+ } | undefined) => import("astro").AstroIntegration & {};
5
+ export default _default;
6
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1,2 @@
1
+ export declare function synthesizeFilename(response: Response): string | null;
2
+ //# sourceMappingURL=filename.d.ts.map
@@ -0,0 +1,2 @@
1
+ export declare function formatOrReturn(filepath: string, source: string): Promise<string>;
2
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1,2 @@
1
+ export declare const onRequest: import("astro").MiddlewareHandler;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,94 @@
1
+ // src/middleware/index.ts
2
+ import { defineMiddleware } from "astro/middleware";
3
+
4
+ // src/middleware/filename.ts
5
+ import mime from "mime-types";
6
+
7
+ // src/middleware/is400.ts
8
+ function is400(status) {
9
+ return status >= 400 && status < 500;
10
+ }
11
+
12
+ // src/middleware/filename.ts
13
+ function synthesizeFilename(response) {
14
+ const contentType = response.headers.get("content-type");
15
+ if (contentType == null) {
16
+ if (is400(response.status)) {
17
+ return "index.html";
18
+ }
19
+ return null;
20
+ }
21
+ const extensionFromContentType = mime.extension(contentType);
22
+ if (!extensionFromContentType) {
23
+ return null;
24
+ }
25
+ return `file.${extensionFromContentType}`;
26
+ }
27
+
28
+ // src/middleware/format.ts
29
+ import { logger } from "@it-astro:logger:astro-prettier-response";
30
+
31
+ // src/middleware/prettier.ts
32
+ import prettier from "prettier";
33
+ import config from "virtual:astro-prettier-response/config";
34
+ function enhanceOptions(options = {}) {
35
+ if (!config.formatXml) {
36
+ return options;
37
+ }
38
+ const givenPlugins = options.plugins ?? [];
39
+ return {
40
+ ...options,
41
+ plugins: [...givenPlugins, "@prettier/plugin-xml"]
42
+ };
43
+ }
44
+ async function format(source, options) {
45
+ return prettier.format(source, enhanceOptions(options));
46
+ }
47
+ async function getFileInfo(source, options) {
48
+ return prettier.getFileInfo(source, enhanceOptions(options));
49
+ }
50
+
51
+ // src/middleware/format.ts
52
+ async function formatOrReturn(filepath, source) {
53
+ try {
54
+ const result = await format(source, {
55
+ filepath
56
+ });
57
+ logger.debug(`successfully formatted`);
58
+ return result;
59
+ } catch (e) {
60
+ logger.error(`failed to format`);
61
+ return source;
62
+ }
63
+ }
64
+
65
+ // src/middleware/index.ts
66
+ import { logger as logger2 } from "@it-astro:logger:astro-prettier-response";
67
+ var onRequest = defineMiddleware(async (context, next) => {
68
+ const response = await next();
69
+ if (!response.ok && !is400(response.status)) {
70
+ logger2.debug(`NOT formatting (status is not 2xx or 4xx)`);
71
+ return response;
72
+ }
73
+ const filename = synthesizeFilename(response);
74
+ if (filename == null) {
75
+ logger2.debug(`NOT formatting (couldn't determine filename/extension)`);
76
+ return response;
77
+ }
78
+ const fileInfo = await getFileInfo(filename);
79
+ if (fileInfo?.inferredParser == null) {
80
+ logger2.debug(`NOT formatting (Prettier has no parser for this file type)`);
81
+ return response;
82
+ }
83
+ const clone = response.clone();
84
+ const source = await clone.text();
85
+ const processedSource = await formatOrReturn(filename, source);
86
+ return new Response(processedSource, {
87
+ status: response.status,
88
+ headers: clone.headers,
89
+ statusText: clone.statusText
90
+ });
91
+ });
92
+ export {
93
+ onRequest
94
+ };
@@ -0,0 +1,2 @@
1
+ export declare function is400(status: number): boolean;
2
+ //# sourceMappingURL=is400.d.ts.map
@@ -0,0 +1,4 @@
1
+ import prettier, { type Options } from "prettier";
2
+ export declare function format(source: string, options?: Options): Promise<string>;
3
+ export declare function getFileInfo(source: string, options?: Options): Promise<prettier.FileInfoResult>;
4
+ //# sourceMappingURL=prettier.d.ts.map
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@altano/astro-prettier-response",
3
+ "version": "0.0.1",
4
+ "description": "Prettier html/css/js for your Astro site",
5
+ "keywords": [
6
+ "astro",
7
+ "astro-integration",
8
+ "withastro"
9
+ ],
10
+ "homepage": "https://github.com/altano/npm-packages/tree/main/packages/astro-prettier-response",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/altano/npm-packages",
14
+ "directory": "packages/astro-prettier-response"
15
+ },
16
+ "license": "ISC",
17
+ "author": {
18
+ "name": "Alan Norbauer",
19
+ "email": "altano@gmail.com",
20
+ "url": "https://alan.norbauer.com"
21
+ },
22
+ "sideEffects": false,
23
+ "type": "module",
24
+ "exports": {
25
+ ".": "./dist/index.js",
26
+ "./middleware": "./dist/middleware/index.js"
27
+ },
28
+ "files": [
29
+ "dist/**/*.js",
30
+ "dist/**/*.d.ts"
31
+ ],
32
+ "dependencies": {
33
+ "@inox-tools/runtime-logger": "^0.4.2",
34
+ "astro-integration-kit": "^0.18.0",
35
+ "mime-types": "^3.0.1"
36
+ },
37
+ "devDependencies": {
38
+ "@astrojs/check": "^0.9.4",
39
+ "@inox-tools/astro-tests": "0.5.1",
40
+ "@prettier/plugin-xml": "^3.4.1",
41
+ "@types/mime-types": "^2.1.4",
42
+ "@types/node": "^22.13.10",
43
+ "@vitest/coverage-v8": "^3.1.1",
44
+ "astro": "^5.5.6",
45
+ "depcheck": "^1.4.7",
46
+ "prettier": "^3.5.3",
47
+ "tsup": "^8.4.0",
48
+ "typescript": "^5.8.2",
49
+ "vite": "^6.2.6",
50
+ "vitest": "^3.1.1",
51
+ "zx": "^8.5.2",
52
+ "@altano/build-config": "0.0.0",
53
+ "@altano/tiny-async-pool": "3.0.2",
54
+ "@altano/testing": "0.0.0",
55
+ "@altano/tsconfig": "0.0.0"
56
+ },
57
+ "peerDependencies": {
58
+ "astro": "5.x.x",
59
+ "prettier": "3.x.x"
60
+ },
61
+ "optionalDependencies": {
62
+ "@prettier/plugin-xml": "3.x.x"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "scripts": {
68
+ "build": "tsup --config ./tsup.config.ts",
69
+ "build:types": "tsc --project tsconfig.declarations.json",
70
+ "check:types:astro-fixtures": "node --experimental-strip-types ./scripts/check-fixtures.ts",
71
+ "check:types:src": "tsc --noEmit",
72
+ "check:types:test": "tsc --noEmit --project ./tests/tsconfig.json",
73
+ "check:unused-dependencies": "depcheck",
74
+ "clean": "rm -rf .turbo && rm -rf .tsbuildinfo && rm -rf node_modules && rm -rf dist",
75
+ "dev": "tsup --watch",
76
+ "format": "prettier --write src",
77
+ "lint": "eslint",
78
+ "lint:fix": "TIMING=1 eslint src/**/*.ts* --fix",
79
+ "lint:timing": "TIMING=1 eslint src/**/*.ts*",
80
+ "test:unit": "vitest --run",
81
+ "test:unit:watch": "vitest"
82
+ }
83
+ }
Binary file