actionview-svelte-handler 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ts/builder.ts ADDED
@@ -0,0 +1,175 @@
1
+ import { readable, Stores } from 'svelte/store'
2
+ import { importFromStringSync } from 'module-from-string'
3
+ import * as esbuild from 'esbuild'
4
+ import sveltePlugin from 'esbuild-svelte'
5
+ import { sveltePreprocess } from 'svelte-preprocess'
6
+ import type { Warning } from 'svelte/types/compiler/interfaces'
7
+ import * as recast from 'recast'
8
+
9
+ export interface BuildSuccess {
10
+ client: string
11
+ server: {
12
+ html: string
13
+ css: string
14
+ head: string
15
+ } | null
16
+ compiled: {
17
+ client: string
18
+ server: string | undefined
19
+ }
20
+ }
21
+
22
+ export interface BuildError {
23
+ error: any
24
+ }
25
+
26
+ export type BuildResult = BuildSuccess | BuildError
27
+
28
+ /**
29
+ * Builder is a class that builds, compiles and bundles Svelte components into a nice object for the template handler
30
+ */
31
+ class Builder {
32
+ path: string
33
+ props: Stores
34
+ locals: object
35
+ compiled: {
36
+ client: string | null
37
+ server: string | null
38
+ }
39
+
40
+ ssr: boolean
41
+ workingDir: string
42
+ preprocess: object
43
+
44
+ constructor (
45
+ path: string,
46
+ props: object,
47
+ locals: object,
48
+ client: string | null,
49
+ server: string | null,
50
+ ssr: boolean,
51
+ workingDir: string,
52
+ preprocess: object
53
+ ) {
54
+ this.path = path
55
+ this.props = readable(props)
56
+ this.locals = locals
57
+ this.compiled = {
58
+ client,
59
+ server
60
+ }
61
+ this.ssr = ssr
62
+ this.workingDir = workingDir
63
+ this.preprocess = preprocess
64
+ }
65
+
66
+ async bundle (generate: 'ssr' | 'dom', sveltePath = 'svelte'): Promise<string> {
67
+ const bundle = await esbuild.build({
68
+ entryPoints: [this.path],
69
+ mainFields: ['svelte', 'browser', 'module', 'main'],
70
+ conditions: ['svelte', 'browser'],
71
+ absWorkingDir: this.workingDir,
72
+ write: false,
73
+ outfile: 'component.js',
74
+ bundle: true,
75
+ format: 'esm',
76
+ metafile: true,
77
+ plugins: [
78
+ // @ts-expect-error
79
+ sveltePlugin({
80
+ compilerOptions: {
81
+ generate,
82
+ css: 'injected',
83
+ hydratable: true,
84
+ sveltePath
85
+ },
86
+ preprocess: sveltePreprocess(this.preprocess),
87
+ filterWarnings: (warning: Warning) => {
88
+ if (
89
+ warning.code === 'missing-declaration' &&
90
+ warning.message.includes("'props'")
91
+ ) {
92
+ return false
93
+ }
94
+ return true
95
+ }
96
+ })
97
+ ]
98
+ })
99
+
100
+ // @ts-expect-error
101
+ const throwables = [].concat(bundle.errors, bundle.warnings)
102
+
103
+ if (throwables.length > 0) {
104
+ throw throwables[0] // eslint-disable-line @typescript-eslint/no-throw-literal
105
+ }
106
+
107
+ return bundle.outputFiles[0].text
108
+ }
109
+
110
+ async client (): Promise<string> {
111
+ return (
112
+ this.compiled?.client || (this.standardizeClient(await this.bundle('dom', 'https://esm.sh/svelte'))) // eslint-disable-line
113
+ )
114
+ }
115
+
116
+ standardizeClient (code: string): string {
117
+ const ast = recast.parse(code)
118
+
119
+ let name: string | undefined
120
+
121
+ recast.visit(ast, {
122
+ visitExportNamedDeclaration: (path) => {
123
+ const stagingName: any = path.node?.specifiers?.[0].local?.name
124
+ name = typeof stagingName !== 'string' ? '' : stagingName
125
+ path.prune()
126
+ return false
127
+ }
128
+ })
129
+
130
+ recast.visit(ast, {
131
+ visitIdentifier: (path) => {
132
+ if (path.node.name === name) {
133
+ path.node.name = 'App'
134
+ }
135
+ return false
136
+ }
137
+ })
138
+
139
+ return recast.print(ast).code
140
+ }
141
+
142
+ async server (): Promise<{ output: string, html: string, css: string, head: string }> {
143
+ const output = this.compiled?.server || (await this.bundle('ssr')) // eslint-disable-line
144
+
145
+ const Component = importFromStringSync(output, {
146
+ globals: { props: this.props }
147
+ }).default
148
+
149
+ const { html, css, head } = await Component.render(this.locals)
150
+
151
+ return { output, html, head, css: css.code }
152
+ }
153
+
154
+ async build (): Promise<BuildResult> {
155
+ try {
156
+ const serv = this.ssr ? await this.server() : null
157
+ const cli = await this.client()
158
+
159
+ const comp = {
160
+ client: cli,
161
+ server: serv?.output
162
+ }
163
+
164
+ return {
165
+ client: cli,
166
+ server: serv,
167
+ compiled: comp
168
+ }
169
+ } catch (e) {
170
+ return { error: e }
171
+ }
172
+ }
173
+ }
174
+
175
+ export default Builder
@@ -0,0 +1,2 @@
1
+ > [!CAUTION]
2
+ > This folder is intended to be built from TypeScript files with `npm run build`
@@ -0,0 +1,44 @@
1
+ import { Stores } from 'svelte/store';
2
+ export interface BuildSuccess {
3
+ client: string;
4
+ server: {
5
+ html: string;
6
+ css: string;
7
+ head: string;
8
+ } | null;
9
+ compiled: {
10
+ client: string;
11
+ server: string | undefined;
12
+ };
13
+ }
14
+ export interface BuildError {
15
+ error: any;
16
+ }
17
+ export type BuildResult = BuildSuccess | BuildError;
18
+ /**
19
+ * Builder is a class that builds, compiles and bundles Svelte components into a nice object for the template handler
20
+ */
21
+ declare class Builder {
22
+ path: string;
23
+ props: Stores;
24
+ locals: object;
25
+ compiled: {
26
+ client: string | null;
27
+ server: string | null;
28
+ };
29
+ ssr: boolean;
30
+ workingDir: string;
31
+ preprocess: object;
32
+ constructor(path: string, props: object, locals: object, client: string | null, server: string | null, ssr: boolean, workingDir: string, preprocess: object);
33
+ bundle(generate: 'ssr' | 'dom', sveltePath?: string): Promise<string>;
34
+ client(): Promise<string>;
35
+ standardizeClient(code: string): string;
36
+ server(): Promise<{
37
+ output: string;
38
+ html: string;
39
+ css: string;
40
+ head: string;
41
+ }>;
42
+ build(): Promise<BuildResult>;
43
+ }
44
+ export default Builder;