@eighty4/dank 0.0.2 → 0.0.4-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/lib/esbuild.ts CHANGED
@@ -1,72 +1,109 @@
1
+ import { readFile } from 'node:fs/promises'
1
2
  import esbuild, {
2
3
  type BuildContext,
3
4
  type BuildOptions,
4
5
  type BuildResult,
5
6
  type Message,
6
- type Metafile,
7
+ type PartialMessage,
8
+ type Plugin,
9
+ type PluginBuild,
7
10
  } from 'esbuild'
11
+ import type { EsbuildConfig } from './dank.ts'
8
12
  import type { DefineDankGlobal } from './define.ts'
9
- import { willMinify } from './flags.ts'
13
+ import type { DankBuild } from './flags.ts'
14
+ import type { BuildRegistry, WebsiteRegistry } from './metadata.ts'
10
15
 
11
- const jsBuildOptions: BuildOptions & { metafile: true; write: true } = {
12
- bundle: true,
13
- metafile: true,
14
- minify: willMinify(),
15
- platform: 'browser',
16
- splitting: false,
17
- treeShaking: true,
18
- write: true,
19
- }
20
-
21
- const webpageBuildOptions: BuildOptions & { metafile: true; write: true } = {
22
- assetNames: 'assets/[name]-[hash]',
23
- format: 'esm',
24
- loader: {
25
- '.tff': 'file',
26
- '.woff': 'file',
27
- '.woff2': 'file',
28
- },
29
- ...jsBuildOptions,
30
- }
16
+ export type EntryPoint = { in: string; out: string }
31
17
 
32
18
  export async function esbuildDevContext(
19
+ b: DankBuild,
20
+ r: WebsiteRegistry,
33
21
  define: DefineDankGlobal,
34
- entryPoints: Array<{ in: string; out: string }>,
35
- outdir: string,
22
+ entryPoints: Array<EntryPoint>,
23
+ c?: EsbuildConfig,
36
24
  ): Promise<BuildContext> {
37
25
  return await esbuild.context({
38
26
  define,
39
27
  entryNames: '[dir]/[name]',
40
- entryPoints: removeEntryPointOutExt(entryPoints),
41
- outdir,
42
- ...webpageBuildOptions,
43
- metafile: false,
28
+ entryPoints: mapEntryPointPaths(entryPoints),
29
+ outdir: b.dirs.buildWatch,
30
+ ...commonBuildOptions(b, r, c),
31
+ splitting: false,
44
32
  write: false,
45
33
  })
46
34
  }
47
35
 
48
36
  export async function esbuildWebpages(
37
+ b: DankBuild,
38
+ r: WebsiteRegistry,
49
39
  define: DefineDankGlobal,
50
- entryPoints: Array<{ in: string; out: string }>,
51
- outdir: string,
52
- ): Promise<Metafile> {
53
- const buildResult = await esbuild.build({
40
+ entryPoints: Array<EntryPoint>,
41
+ c?: EsbuildConfig,
42
+ ): Promise<void> {
43
+ const result = await esbuild.build({
54
44
  define,
55
45
  entryNames: '[dir]/[name]-[hash]',
56
- entryPoints: removeEntryPointOutExt(entryPoints),
57
- outdir,
58
- ...webpageBuildOptions,
46
+ entryPoints: mapEntryPointPaths(entryPoints),
47
+ outdir: b.dirs.buildDist,
48
+ ...commonBuildOptions(b, r, c),
59
49
  })
60
- esbuildResultChecks(buildResult)
61
- return buildResult.metafile
50
+ esbuildResultChecks(result)
51
+ }
52
+
53
+ export async function esbuildWorkers(
54
+ b: DankBuild,
55
+ r: WebsiteRegistry,
56
+ define: DefineDankGlobal,
57
+ entryPoints: Array<EntryPoint>,
58
+ c?: EsbuildConfig,
59
+ ): Promise<void> {
60
+ const result = await esbuild.build({
61
+ define,
62
+ entryNames: '[dir]/[name]-[hash]',
63
+ entryPoints: mapEntryPointPaths(entryPoints),
64
+ outdir: b.dirs.buildDist,
65
+ ...commonBuildOptions(b, r, c),
66
+ splitting: false,
67
+ metafile: true,
68
+ write: true,
69
+ assetNames: 'assets/[name]-[hash]',
70
+ })
71
+ esbuildResultChecks(result)
72
+ }
73
+
74
+ function commonBuildOptions(
75
+ b: DankBuild,
76
+ r: WebsiteRegistry,
77
+ c?: EsbuildConfig,
78
+ ): BuildOptions {
79
+ const p = workersPlugin(r.buildRegistry())
80
+ return {
81
+ absWorkingDir: b.dirs.projectRootAbs,
82
+ assetNames: 'assets/[name]-[hash]',
83
+ bundle: true,
84
+ format: 'esm',
85
+ loader: c?.loaders || defaultLoaders(),
86
+ metafile: true,
87
+ minify: b.minify,
88
+ platform: 'browser',
89
+ plugins: c?.plugins?.length ? [p, ...c.plugins] : [p],
90
+ splitting: true,
91
+ treeShaking: true,
92
+ write: true,
93
+ }
94
+ }
95
+
96
+ function defaultLoaders(): BuildOptions['loader'] {
97
+ return {
98
+ '.woff': 'file',
99
+ '.woff2': 'file',
100
+ }
62
101
  }
63
102
 
64
103
  // esbuild will append the .js or .css to output filenames
65
104
  // keeping extension on entryPoints data for consistency
66
- // and removing and mapping entryPoints to pass to esbuild
67
- function removeEntryPointOutExt(
68
- entryPoints: Array<{ in: string; out: string }>,
69
- ) {
105
+ // and only trimming when creating esbuild opts
106
+ function mapEntryPointPaths(entryPoints: Array<EntryPoint>) {
70
107
  return entryPoints.map(entryPoint => {
71
108
  return {
72
109
  in: entryPoint.in,
@@ -95,3 +132,117 @@ function esbuildPrintMessage(msg: Message, category: 'error' | 'warning') {
95
132
  if (note.location) console.error(' ', note.location)
96
133
  })
97
134
  }
135
+
136
+ const WORKER_CTOR_REGEX =
137
+ /new(?:\s|\r?\n)+Worker(?:\s|\r?\n)*\((?:\s|\r?\n)*(?<url>.*)(?:\s|\r?\n)*\)/g
138
+ const WORKER_URL_REGEX = /^('.*'|".*")$/
139
+
140
+ export function workersPlugin(r: BuildRegistry): Plugin {
141
+ return {
142
+ name: '@eighty4/dank/esbuild/workers',
143
+ setup(build: PluginBuild) {
144
+ if (!build.initialOptions.absWorkingDir)
145
+ throw TypeError('plugin requires absWorkingDir')
146
+ if (!build.initialOptions.metafile)
147
+ throw TypeError('plugin requires metafile')
148
+ const { absWorkingDir } = build.initialOptions
149
+
150
+ build.onLoad({ filter: /\.(t|m?j)s$/ }, async args => {
151
+ let contents = await readFile(args.path, 'utf8')
152
+ let offset = 0
153
+ let errors: Array<PartialMessage> | undefined = undefined
154
+ for (const workerCtorMatch of contents.matchAll(
155
+ WORKER_CTOR_REGEX,
156
+ )) {
157
+ const workerUrlString = workerCtorMatch.groups!.url
158
+ if (WORKER_URL_REGEX.test(workerUrlString)) {
159
+ const preamble = contents.substring(
160
+ 0,
161
+ workerCtorMatch.index,
162
+ )
163
+ const lineIndex = preamble.lastIndexOf('\n') || 0
164
+ const lineCommented = /\/\//.test(
165
+ preamble.substring(lineIndex),
166
+ )
167
+ if (lineCommented) continue
168
+ const blockCommentIndex = preamble.lastIndexOf('/*')
169
+ const blockCommented =
170
+ blockCommentIndex !== -1 &&
171
+ preamble
172
+ .substring(blockCommentIndex)
173
+ .indexOf('*/') === -1
174
+ if (blockCommented) continue
175
+ const clientScript = args.path
176
+ .replace(absWorkingDir, '')
177
+ .substring(1)
178
+ const workerUrl = workerUrlString.substring(
179
+ 1,
180
+ workerUrlString.length - 1,
181
+ )
182
+ // todo out of bounds error on path resolve
183
+ const workerEntryPoint = r.resolve(
184
+ clientScript,
185
+ workerUrl,
186
+ )
187
+ const workerUrlPlaceholder = workerEntryPoint
188
+ .replace(/^pages/, '')
189
+ .replace(/\.(t|m?j)s$/, '.js')
190
+ const workerCtorReplacement = `new Worker('${workerUrlPlaceholder}')`
191
+ contents =
192
+ contents.substring(
193
+ 0,
194
+ workerCtorMatch.index + offset,
195
+ ) +
196
+ workerCtorReplacement +
197
+ contents.substring(
198
+ workerCtorMatch.index +
199
+ workerCtorMatch[0].length +
200
+ offset,
201
+ )
202
+ offset +=
203
+ workerCtorReplacement.length -
204
+ workerCtorMatch[0].length
205
+ r.addWorker({
206
+ clientScript,
207
+ workerEntryPoint,
208
+ workerUrl,
209
+ workerUrlPlaceholder,
210
+ })
211
+ } else {
212
+ if (!errors) errors = []
213
+ const preamble = contents.substring(
214
+ 0,
215
+ workerCtorMatch.index,
216
+ )
217
+ const line = preamble.match(/\n/g)?.length || 0
218
+ const lineIndex = preamble.lastIndexOf('\n') || 0
219
+ const column = preamble.length - lineIndex
220
+ const lineText = contents.substring(
221
+ lineIndex,
222
+ contents.indexOf('\n', lineIndex) ||
223
+ contents.length,
224
+ )
225
+ errors.push({
226
+ id: 'worker-url-unresolvable',
227
+ location: {
228
+ lineText,
229
+ line,
230
+ column,
231
+ file: args.path,
232
+ length: workerCtorMatch[0].length,
233
+ },
234
+ })
235
+ }
236
+ }
237
+ const loader = args.path.endsWith('ts') ? 'ts' : 'js'
238
+ return { contents, errors, loader }
239
+ })
240
+
241
+ build.onEnd((result: BuildResult<{ metafile: true }>) => {
242
+ if (result.metafile) {
243
+ r.completeBuild(result)
244
+ }
245
+ })
246
+ },
247
+ }
248
+ }
package/lib/flags.ts CHANGED
@@ -1,18 +1,160 @@
1
+ import { join, resolve } from 'node:path'
2
+ import { cwd } from 'node:process'
3
+ import type { DankConfig } from './dank.ts'
4
+
5
+ export type DankBuild = {
6
+ dirs: ProjectDirs
7
+ minify: boolean
8
+ production: boolean
9
+ }
10
+
11
+ type ProjectDirs = {
12
+ buildRoot: string
13
+ buildWatch: string
14
+ buildDist: string
15
+ pages: string
16
+ pagesResolved: string
17
+ projectRootAbs: string
18
+ public: string
19
+ }
20
+
21
+ export function resolveBuildFlags(): DankBuild {
22
+ const flags: DankBuild = {
23
+ dirs: defaultProjectDirs(cwd()),
24
+ minify: willMinify(),
25
+ production: isProductionBuild(),
26
+ }
27
+ return {
28
+ get dirs(): ProjectDirs {
29
+ return flags.dirs
30
+ },
31
+ get minify(): boolean {
32
+ return flags.minify
33
+ },
34
+ get production(): boolean {
35
+ return flags.production
36
+ },
37
+ }
38
+ }
39
+
40
+ export type DankServe = DankBuild & {
41
+ dankPort: number
42
+ esbuildPort: number
43
+ logHttp: boolean
44
+ preview: boolean
45
+ }
46
+
47
+ export function resolveServeFlags(c: DankConfig): DankServe {
48
+ const preview = isPreviewBuild()
49
+ const flags: DankServe = {
50
+ dirs: defaultProjectDirs(cwd()),
51
+ dankPort: dankPort(c, preview),
52
+ esbuildPort: esbuildPort(c),
53
+ logHttp: willLogHttp(),
54
+ minify: willMinify(),
55
+ preview,
56
+ production: isProductionBuild(),
57
+ }
58
+ return {
59
+ get dirs(): ProjectDirs {
60
+ return flags.dirs
61
+ },
62
+ get dankPort(): number {
63
+ return flags.dankPort
64
+ },
65
+ get esbuildPort(): number {
66
+ return flags.esbuildPort
67
+ },
68
+ get logHttp(): boolean {
69
+ return flags.logHttp
70
+ },
71
+ get minify(): boolean {
72
+ return flags.minify
73
+ },
74
+ get preview(): boolean {
75
+ return flags.preview
76
+ },
77
+ get production(): boolean {
78
+ return flags.production
79
+ },
80
+ }
81
+ }
82
+
1
83
  // `dank serve` will pre-bundle and use service worker
2
- export const isPreviewBuild = () =>
84
+ const isPreviewBuild = () =>
3
85
  process.env.PREVIEW === 'true' || process.argv.includes('--preview')
4
86
 
5
87
  // `dank build` will minify sources and append git release tag to build tag
6
88
  // `dank serve` will pre-bundle with service worker and minify
7
- export const isProductionBuild = () =>
89
+ const isProductionBuild = () =>
8
90
  process.env.PRODUCTION === 'true' || process.argv.includes('--production')
9
91
 
10
- export const willMinify = () =>
92
+ // `dank serve` dank port for frontend webserver
93
+ // alternate --preview port for service worker builds
94
+ function dankPort(c: DankConfig, preview: boolean): number {
95
+ if (process.env.DANK_PORT?.length) {
96
+ return parsePortEnvVar('DANK_PORT')
97
+ }
98
+ return preview ? c.previewPort || c.port || 4000 : c.port || 3000
99
+ }
100
+
101
+ // `dank serve` esbuild port for bundler integration
102
+ function esbuildPort(c: DankConfig): number {
103
+ if (process.env.ESBUILD_PORT?.length) {
104
+ return parsePortEnvVar('ESBUILD_PORT')
105
+ }
106
+ return c.esbuild?.port || 3995
107
+ }
108
+
109
+ function parsePortEnvVar(name: string): number {
110
+ const port = parseInt(process.env[name]!, 10)
111
+ if (isNaN(port)) {
112
+ throw Error(`env var ${name}=${port} must be a valid port number`)
113
+ } else {
114
+ return port
115
+ }
116
+ }
117
+
118
+ export function defaultProjectDirs(projectRootAbs: string): ProjectDirs {
119
+ const dirs: ProjectDirs = {
120
+ buildRoot: 'build',
121
+ buildDist: join('build', 'dist'),
122
+ buildWatch: join('build', 'watch'),
123
+ pages: 'pages',
124
+ pagesResolved: resolve(join(projectRootAbs, 'pages')),
125
+ projectRootAbs,
126
+ public: 'public',
127
+ }
128
+ return {
129
+ get buildRoot(): string {
130
+ return dirs.buildRoot
131
+ },
132
+ get buildDist(): string {
133
+ return dirs.buildDist
134
+ },
135
+ get buildWatch(): string {
136
+ return dirs.buildWatch
137
+ },
138
+ get pages(): string {
139
+ return dirs.pages
140
+ },
141
+ get pagesResolved(): string {
142
+ return dirs.pagesResolved
143
+ },
144
+ get projectRootAbs(): string {
145
+ return dirs.projectRootAbs
146
+ },
147
+ get public(): string {
148
+ return dirs.public
149
+ },
150
+ }
151
+ }
152
+
153
+ const willMinify = () =>
11
154
  isProductionBuild() ||
12
155
  process.env.MINIFY === 'true' ||
13
156
  process.argv.includes('--minify')
14
157
 
15
- export const willTsc = () =>
16
- isProductionBuild() ||
17
- process.env.TSC === 'true' ||
18
- process.argv.includes('--tsc')
158
+ // `dank serve` will print http access logs to console
159
+ const willLogHttp = () =>
160
+ process.env.LOG_HTTP === 'true' || process.argv.includes('--log-http')