@athenna/http 5.12.0 → 5.13.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@athenna/http",
3
- "version": "5.12.0",
3
+ "version": "5.13.0",
4
4
  "description": "The Athenna Http server. Built on top of fastify.",
5
5
  "license": "MIT",
6
6
  "author": "João Lenon <lenon@athenna.io>",
@@ -9,5 +9,6 @@
9
9
  import { ServiceProvider } from '@athenna/ioc';
10
10
  export declare class HttpServerProvider extends ServiceProvider {
11
11
  register(): void;
12
+ boot(): void;
12
13
  shutdown(): Promise<void>;
13
14
  }
@@ -6,12 +6,41 @@
6
6
  * For the full copyright and license information, please view the LICENSE
7
7
  * file that was distributed with this source code.
8
8
  */
9
+ import { View } from '@athenna/view';
10
+ import { Vite } from '#src/vite/index';
11
+ import { EdgeError } from 'edge-error';
9
12
  import { ServiceProvider } from '@athenna/ioc';
10
13
  import { ServerImpl } from '#src/server/ServerImpl';
11
14
  export class HttpServerProvider extends ServiceProvider {
12
15
  register() {
13
16
  this.container.instance('Athenna/Core/HttpServer', new ServerImpl(Config.get('http.fastify')));
14
17
  }
18
+ boot() {
19
+ View.edge.global('vite', new Vite());
20
+ View.edge.registerTag({
21
+ tagName: 'vite',
22
+ seekable: true,
23
+ block: false,
24
+ compile(parser, buffer, token) {
25
+ /**
26
+ * Ensure an argument is defined
27
+ */
28
+ if (!token.properties.jsArg.trim()) {
29
+ throw new EdgeError('Missing entrypoint name', 'E_RUNTIME_EXCEPTION', {
30
+ filename: token.filename,
31
+ line: token.loc.start.line,
32
+ col: token.loc.start.col
33
+ });
34
+ }
35
+ const parsed = parser.utils.transformAst(parser.utils.generateAST(token.properties.jsArg, token.loc, token.filename), token.filename, parser);
36
+ const entrypoints = parser.utils.stringify(parsed);
37
+ const methodCall = parsed.type === 'SequenceExpression'
38
+ ? `generateEntryPointsTags${entrypoints}`
39
+ : `generateEntryPointsTags(${entrypoints})`;
40
+ buffer.outputExpression(`(await state.vite.${methodCall}).join('\\n')`, token.filename, token.loc.start.line, false);
41
+ }
42
+ });
43
+ }
15
44
  async shutdown() {
16
45
  const Server = this.container.use('Athenna/Core/HttpServer');
17
46
  if (!Server) {
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @athenna/http
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import type { Manifest, ModuleNode } from 'vite';
10
+ export declare class Vite {
11
+ /**
12
+ * We cache the manifest file content in production
13
+ * to avoid reading the file multiple times.
14
+ */
15
+ manifestCache?: Manifest;
16
+ /**
17
+ * Verify if vite is running in development mode.
18
+ */
19
+ get isViteRunning(): boolean;
20
+ /**
21
+ * Reads the file contents as JSON.
22
+ */
23
+ readFileAsJSON(filePath: string): any;
24
+ /**
25
+ * Returns a new array with unique items by the given key
26
+ */
27
+ uniqueBy<T>(array: T[], key: keyof T): T[];
28
+ /**
29
+ * Convert Record of attributes to a valid HTML string.
30
+ */
31
+ makeAttributes(attributes: Record<string, string | boolean>): string;
32
+ /**
33
+ * Generates a JSON element with a custom toString implementation.
34
+ */
35
+ generateElement(element: any): any;
36
+ /**
37
+ * Returns the script needed for the HMR working with Vite.
38
+ */
39
+ getViteHmrScript(): any;
40
+ /**
41
+ * Check if the given path is a CSS path.
42
+ */
43
+ isCssPath(path: string): boolean;
44
+ /**
45
+ * If the module is a style module.
46
+ */
47
+ isStyleModule(mod: ModuleNode): boolean;
48
+ /**
49
+ * Create a style tag for the given path
50
+ */
51
+ makeStyleTag(url: string, attributes?: any): any;
52
+ /**
53
+ * Create a script tag for the given path
54
+ */
55
+ makeScriptTag(url: string, attributes?: any): any;
56
+ /**
57
+ * Generate a HTML tag for the given asset
58
+ */
59
+ generateTag(asset: string, attributes?: any): any;
60
+ /**
61
+ * Get a chunk from the manifest file for a given file name
62
+ */
63
+ chunk(manifest: Manifest, entrypoint: string): import("vite").ManifestChunk;
64
+ /**
65
+ * Get a list of chunks for a given filename
66
+ */
67
+ chunksByFile(manifest: Manifest, file: string): import("vite").ManifestChunk[];
68
+ /**
69
+ * Generate preload tag for a given url
70
+ */
71
+ makePreloadTagForUrl(url: string): any;
72
+ /**
73
+ * Generate style and script tags for the given entrypoints
74
+ * Also adds the @vite/client script
75
+ */
76
+ generateEntryPointsTagsForDevMode(entryPoints: string[], attributes?: any): Promise<any[]>;
77
+ /**
78
+ * Generate style and script tags for the given entrypoints
79
+ * using the manifest file
80
+ */
81
+ generateEntryPointsTagsWithManifest(entryPoints: string[], attributes?: any): any[];
82
+ /**
83
+ * Generate tags for the entry points
84
+ */
85
+ generateEntryPointsTags(entryPoints: string[] | string, attributes?: any): Promise<any[]>;
86
+ /**
87
+ * Returns the manifest file contents
88
+ *
89
+ * @throws Will throw an exception when running in dev
90
+ */
91
+ manifest(): Manifest;
92
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * @athenna/http
3
+ *
4
+ * (c) João Lenon <lenon@athenna.io>
5
+ *
6
+ * For the full copyright and license information, please view the LICENSE
7
+ * file that was distributed with this source code.
8
+ */
9
+ import { File, Path } from '@athenna/common';
10
+ const styleFileRegex = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)($|\?)/;
11
+ export class Vite {
12
+ /**
13
+ * Verify if vite is running in development mode.
14
+ */
15
+ get isViteRunning() {
16
+ return process.argv.includes('--vite');
17
+ }
18
+ /**
19
+ * Reads the file contents as JSON.
20
+ */
21
+ readFileAsJSON(filePath) {
22
+ return new File(filePath).getContentAsJsonSync();
23
+ }
24
+ /**
25
+ * Returns a new array with unique items by the given key
26
+ */
27
+ uniqueBy(array, key) {
28
+ const seen = new Set();
29
+ return array.filter(item => {
30
+ const k = item[key];
31
+ return seen.has(k) ? false : seen.add(k);
32
+ });
33
+ }
34
+ /**
35
+ * Convert Record of attributes to a valid HTML string.
36
+ */
37
+ makeAttributes(attributes) {
38
+ return Object.keys(attributes)
39
+ .map(key => {
40
+ const value = attributes[key];
41
+ if (value === true) {
42
+ return key;
43
+ }
44
+ if (!value) {
45
+ return null;
46
+ }
47
+ return `${key}="${value}"`;
48
+ })
49
+ .filter(attr => attr !== null)
50
+ .join(' ');
51
+ }
52
+ /**
53
+ * Generates a JSON element with a custom toString implementation.
54
+ */
55
+ generateElement(element) {
56
+ return {
57
+ ...element,
58
+ toString() {
59
+ const attributes = `${this.makeAttributes(element.attributes)}`;
60
+ if (element.tag === 'link') {
61
+ return `<${element.tag} ${attributes}/>`;
62
+ }
63
+ return `<${element.tag} ${attributes}>${element.children.join('\n')}</${element.tag}>`;
64
+ }
65
+ };
66
+ }
67
+ /**
68
+ * Returns the script needed for the HMR working with Vite.
69
+ */
70
+ getViteHmrScript() {
71
+ return this.generateElement({
72
+ tag: 'script',
73
+ attributes: {
74
+ type: 'module',
75
+ src: '/@vite/client'
76
+ },
77
+ children: []
78
+ });
79
+ }
80
+ /**
81
+ * Check if the given path is a CSS path.
82
+ */
83
+ isCssPath(path) {
84
+ return path.match(styleFileRegex) !== null;
85
+ }
86
+ /**
87
+ * If the module is a style module.
88
+ */
89
+ isStyleModule(mod) {
90
+ if (this.isCssPath(mod.url) ||
91
+ (mod.id && /\?vue&type=style/.test(mod.id))) {
92
+ return true;
93
+ }
94
+ return false;
95
+ }
96
+ /**
97
+ * Create a style tag for the given path
98
+ */
99
+ makeStyleTag(url, attributes) {
100
+ return this.generateElement({
101
+ tag: 'link',
102
+ attributes: { rel: 'stylesheet', href: url, ...attributes }
103
+ });
104
+ }
105
+ /**
106
+ * Create a script tag for the given path
107
+ */
108
+ makeScriptTag(url, attributes) {
109
+ return this.generateElement({
110
+ tag: 'script',
111
+ attributes: { type: 'module', src: url, ...attributes },
112
+ children: []
113
+ });
114
+ }
115
+ /**
116
+ * Generate a HTML tag for the given asset
117
+ */
118
+ generateTag(asset, attributes) {
119
+ let url = '';
120
+ if (this.isViteRunning) {
121
+ url = `/${asset}`;
122
+ }
123
+ else {
124
+ url = asset;
125
+ }
126
+ if (this.isCssPath(asset)) {
127
+ return this.makeStyleTag(url, attributes);
128
+ }
129
+ return this.makeScriptTag(url, attributes);
130
+ }
131
+ /**
132
+ * Get a chunk from the manifest file for a given file name
133
+ */
134
+ chunk(manifest, entrypoint) {
135
+ const chunk = manifest[entrypoint];
136
+ if (!chunk) {
137
+ throw new Error(`Cannot find "${entrypoint}" chunk in the manifest file`);
138
+ }
139
+ return chunk;
140
+ }
141
+ /**
142
+ * Get a list of chunks for a given filename
143
+ */
144
+ chunksByFile(manifest, file) {
145
+ return Object.entries(manifest)
146
+ .filter(([, chunk]) => chunk.file === file)
147
+ .map(([_, chunk]) => chunk);
148
+ }
149
+ /**
150
+ * Generate preload tag for a given url
151
+ */
152
+ makePreloadTagForUrl(url) {
153
+ const attributes = this.isCssPath(url)
154
+ ? { rel: 'preload', as: 'style', href: url }
155
+ : { rel: 'modulepreload', href: url };
156
+ return this.generateElement({ tag: 'link', attributes });
157
+ }
158
+ /**
159
+ * Generate style and script tags for the given entrypoints
160
+ * Also adds the @vite/client script
161
+ */
162
+ async generateEntryPointsTagsForDevMode(entryPoints, attributes) {
163
+ const tags = entryPoints.map(entrypoint => this.generateTag(entrypoint, attributes));
164
+ const viteHmr = this.getViteHmrScript();
165
+ const result = [viteHmr, tags];
166
+ return result.sort(tag => (tag.tag === 'link' ? -1 : 1));
167
+ }
168
+ /**
169
+ * Generate style and script tags for the given entrypoints
170
+ * using the manifest file
171
+ */
172
+ generateEntryPointsTagsWithManifest(entryPoints, attributes) {
173
+ const manifest = this.manifest();
174
+ const tags = [];
175
+ const preloads = [];
176
+ for (const entryPoint of entryPoints) {
177
+ const chunk = this.chunk(manifest, entryPoint);
178
+ preloads.push({ path: chunk.file });
179
+ tags.push({
180
+ path: chunk.file,
181
+ tag: this.generateTag(chunk.file, {
182
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
183
+ // @ts-ignore
184
+ integrity: chunk.integrity
185
+ })
186
+ });
187
+ for (const css of chunk.css || []) {
188
+ preloads.push({ path: css });
189
+ tags.push({ path: css, tag: this.generateTag(css) });
190
+ }
191
+ for (const importNode of chunk.imports || []) {
192
+ preloads.push({ path: manifest[importNode].file });
193
+ for (const css of manifest[importNode].css || []) {
194
+ const subChunk = this.chunksByFile(manifest, css);
195
+ preloads.push({ path: css });
196
+ tags.push({
197
+ path: css,
198
+ tag: this.generateTag(css, {
199
+ ...attributes,
200
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
201
+ // @ts-ignore
202
+ integrity: subChunk[0]?.integrity
203
+ })
204
+ });
205
+ }
206
+ }
207
+ }
208
+ const preloadsElements = this.uniqueBy(preloads, 'path')
209
+ .sort(preload => (this.isCssPath(preload.path) ? -1 : 1))
210
+ .map(preload => this.makePreloadTagForUrl(preload.path));
211
+ return preloadsElements.concat(tags.map(({ tag }) => tag));
212
+ }
213
+ /**
214
+ * Generate tags for the entry points
215
+ */
216
+ async generateEntryPointsTags(entryPoints, attributes) {
217
+ entryPoints = Array.isArray(entryPoints) ? entryPoints : [entryPoints];
218
+ if (this.isViteRunning) {
219
+ return this.generateEntryPointsTagsForDevMode(entryPoints, attributes);
220
+ }
221
+ return this.generateEntryPointsTagsWithManifest(entryPoints, attributes);
222
+ }
223
+ /**
224
+ * Returns the manifest file contents
225
+ *
226
+ * @throws Will throw an exception when running in dev
227
+ */
228
+ manifest() {
229
+ if (this.isViteRunning) {
230
+ throw new Error('Cannot read the manifest file when running in dev mode');
231
+ }
232
+ if (!this.manifestCache) {
233
+ this.manifestCache = this.readFileAsJSON(Path.public('assets/.vite/manifest.json'));
234
+ }
235
+ return this.manifestCache;
236
+ }
237
+ }
@@ -12,14 +12,7 @@ export function athenna(options) {
12
12
  const fullOptions = Object.assign({
13
13
  assetsUrl: '/assets',
14
14
  buildDirectory: 'public/assets',
15
- reload: ['./src/resources/views/**/*.edge'],
16
- css: {
17
- preprocessorOptions: {
18
- scss: {
19
- api: 'modern'
20
- }
21
- }
22
- }
15
+ reload: ['./src/resources/views/**/*.edge']
23
16
  }, options);
24
17
  return [PluginRestart({ reload: fullOptions.reload }), config(fullOptions)];
25
18
  }