@eighty4/dank 0.0.4-2 → 0.0.4

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/dirs.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { realpath } from 'node:fs/promises'
2
+ import { dirname, isAbsolute, join, resolve } from 'node:path'
3
+ import { cwd } from 'node:process'
4
+
5
+ export type DankDirectories = {
6
+ buildRoot: string
7
+ // output dir of html during `dank serve`
8
+ buildWatch: string
9
+ buildDist: string
10
+ pages: string
11
+ pagesResolved: string
12
+ projectResolved: string
13
+ projectRootAbs: string
14
+ public: string
15
+ }
16
+
17
+ export async function defaultProjectDirs(
18
+ projectRootAbs: string,
19
+ ): Promise<Readonly<DankDirectories>> {
20
+ if (!projectRootAbs) {
21
+ projectRootAbs = cwd()
22
+ } else if (!isAbsolute(projectRootAbs)) {
23
+ throw Error()
24
+ }
25
+ const projectResolved = await realpath(projectRootAbs)
26
+ const pages = 'pages'
27
+ const pagesResolved = join(projectResolved, pages)
28
+ return Object.freeze({
29
+ buildRoot: 'build',
30
+ buildDist: join('build', 'dist'),
31
+ buildWatch: join('build', 'watch'),
32
+ pages,
33
+ pagesResolved,
34
+ projectResolved,
35
+ projectRootAbs,
36
+ public: 'public',
37
+ })
38
+ }
39
+
40
+ export type ResolveError = 'outofbounds'
41
+
42
+ export class Resolver {
43
+ #dirs: DankDirectories
44
+
45
+ constructor(dirs: DankDirectories) {
46
+ this.#dirs = dirs
47
+ }
48
+
49
+ // cross platform safe absolute path resolution from pages dir
50
+ absPagesPath(...p: Array<string>): string {
51
+ return join(this.#dirs.projectRootAbs, this.#dirs.pages, ...p)
52
+ }
53
+
54
+ // cross platform safe absolute path resolution from project root
55
+ absProjectPath(...p: Array<string>): string {
56
+ return join(this.#dirs.projectRootAbs, ...p)
57
+ }
58
+
59
+ // `p` is expected to be a relative path resolvable from the project dir
60
+ isProjectSubpathInPagesDir(p: string): boolean {
61
+ return resolve(join(this.#dirs.projectResolved, p)).startsWith(
62
+ this.#dirs.pagesResolved,
63
+ )
64
+ }
65
+
66
+ // `p` is expected to be a relative path resolvable from the pages dir
67
+ isPagesSubpathInPagesDir(p: string): boolean {
68
+ return this.isProjectSubpathInPagesDir(join(this.#dirs.pages, p))
69
+ }
70
+
71
+ // resolve a pages subpath from a resource within the pages directory by a relative href
72
+ // `from` is expected to be a pages resource fs path starting with `pages/` and ending with filename
73
+ // the result will be a pages subpath and will not have the pages dir prefix
74
+ // returns 'outofbounds' if the relative path does not resolve to a file within the pages dir
75
+ resolveHrefInPagesDir(from: string, href: string): string | ResolveError {
76
+ const p = join(dirname(from), href)
77
+ if (this.isProjectSubpathInPagesDir(p)) {
78
+ return p
79
+ } else {
80
+ return 'outofbounds'
81
+ }
82
+ }
83
+ }
package/lib/errors.ts ADDED
@@ -0,0 +1,6 @@
1
+ export class DankError extends Error {
2
+ constructor(message: string, cause?: Error) {
3
+ super(message, { cause })
4
+ this.name = 'DankError'
5
+ }
6
+ }
package/lib/esbuild.ts CHANGED
@@ -9,45 +9,39 @@ import esbuild, {
9
9
  type Plugin,
10
10
  type PluginBuild,
11
11
  } from 'esbuild'
12
- import type { EsbuildConfig } from './dank.ts'
13
12
  import type { DefineDankGlobal } from './define.ts'
14
- import type { DankBuild } from './flags.ts'
15
- import type { BuildRegistry, WebsiteRegistry } from './metadata.ts'
13
+ import type { BuildRegistry, WebsiteRegistry } from './registry.ts'
16
14
 
17
15
  export type EntryPoint = { in: string; out: string }
18
16
 
19
17
  export async function esbuildDevContext(
20
- b: DankBuild,
21
18
  r: WebsiteRegistry,
22
19
  define: DefineDankGlobal,
23
20
  entryPoints: Array<EntryPoint>,
24
- c?: EsbuildConfig,
25
21
  ): Promise<BuildContext> {
26
22
  return await esbuild.context({
27
23
  define,
28
24
  entryNames: '[dir]/[name]',
29
25
  entryPoints: mapEntryPointPaths(entryPoints),
30
- outdir: b.dirs.buildWatch,
31
- ...commonBuildOptions(b, r, c),
26
+ outdir: r.config.dirs.buildWatch,
27
+ ...commonBuildOptions(r),
32
28
  splitting: false,
33
29
  write: false,
34
30
  })
35
31
  }
36
32
 
37
33
  export async function esbuildWebpages(
38
- b: DankBuild,
39
34
  r: WebsiteRegistry,
40
35
  define: DefineDankGlobal,
41
36
  entryPoints: Array<EntryPoint>,
42
- c?: EsbuildConfig,
43
37
  ): Promise<void> {
44
38
  try {
45
39
  await esbuild.build({
46
40
  define,
47
41
  entryNames: '[dir]/[name]-[hash]',
48
42
  entryPoints: mapEntryPointPaths(entryPoints),
49
- outdir: b.dirs.buildDist,
50
- ...commonBuildOptions(b, r, c),
43
+ outdir: r.config.dirs.buildDist,
44
+ ...commonBuildOptions(r),
51
45
  })
52
46
  } catch (ignore) {
53
47
  process.exit(1)
@@ -55,19 +49,17 @@ export async function esbuildWebpages(
55
49
  }
56
50
 
57
51
  export async function esbuildWorkers(
58
- b: DankBuild,
59
52
  r: WebsiteRegistry,
60
53
  define: DefineDankGlobal,
61
54
  entryPoints: Array<EntryPoint>,
62
- c?: EsbuildConfig,
63
55
  ): Promise<void> {
64
56
  try {
65
57
  await esbuild.build({
66
58
  define,
67
59
  entryNames: '[dir]/[name]-[hash]',
68
60
  entryPoints: mapEntryPointPaths(entryPoints),
69
- outdir: b.dirs.buildDist,
70
- ...commonBuildOptions(b, r, c),
61
+ outdir: r.config.dirs.buildDist,
62
+ ...commonBuildOptions(r),
71
63
  splitting: false,
72
64
  metafile: true,
73
65
  write: true,
@@ -78,22 +70,19 @@ export async function esbuildWorkers(
78
70
  }
79
71
  }
80
72
 
81
- function commonBuildOptions(
82
- b: DankBuild,
83
- r: WebsiteRegistry,
84
- c?: EsbuildConfig,
85
- ): BuildOptions {
73
+ function commonBuildOptions(r: WebsiteRegistry): BuildOptions {
86
74
  const p = workersPlugin(r.buildRegistry())
87
75
  return {
88
- absWorkingDir: b.dirs.projectRootAbs,
89
76
  assetNames: 'assets/[name]-[hash]',
90
77
  bundle: true,
91
78
  format: 'esm',
92
- loader: c?.loaders || defaultLoaders(),
79
+ loader: r.config.esbuild?.loaders || defaultLoaders(),
93
80
  metafile: true,
94
- minify: b.minify,
81
+ minify: r.config.flags.minify,
95
82
  platform: 'browser',
96
- plugins: c?.plugins?.length ? [p, ...c.plugins] : [p],
83
+ plugins: r.config.esbuild?.plugins?.length
84
+ ? [p, ...r.config.esbuild?.plugins]
85
+ : [p],
97
86
  splitting: true,
98
87
  treeShaking: true,
99
88
  write: true,
@@ -127,11 +116,8 @@ export function workersPlugin(r: BuildRegistry): Plugin {
127
116
  return {
128
117
  name: '@eighty4/dank/esbuild/workers',
129
118
  setup(build: PluginBuild) {
130
- if (!build.initialOptions.absWorkingDir)
131
- throw TypeError('plugin requires absWorkingDir')
132
119
  if (!build.initialOptions.metafile)
133
120
  throw TypeError('plugin requires metafile')
134
- const { absWorkingDir } = build.initialOptions
135
121
 
136
122
  build.onLoad({ filter: /\.(t|m?j)s$/ }, async args => {
137
123
  let contents = await readFile(args.path, 'utf8')
@@ -158,7 +144,7 @@ export function workersPlugin(r: BuildRegistry): Plugin {
158
144
  continue
159
145
  }
160
146
  const clientScript = args.path
161
- .replace(absWorkingDir, '')
147
+ .replace(r.dirs.projectResolved, '')
162
148
  .substring(1)
163
149
  const workerUrl = workerCtorMatch.groups!.url.substring(
164
150
  1,
@@ -182,10 +168,13 @@ export function workersPlugin(r: BuildRegistry): Plugin {
182
168
  )
183
169
  continue
184
170
  }
171
+ const workerCtor = workerCtorMatch.groups!.ctor as
172
+ | 'Worker'
173
+ | 'SharedWorker'
185
174
  const workerUrlPlaceholder = workerEntryPoint
186
175
  .replace(/^pages/, '')
187
176
  .replace(/\.(t|m?j)s$/, '.js')
188
- const workerCtorReplacement = `new ${workerCtorMatch.groups!.ctor}('${workerUrlPlaceholder}'${workerCtorMatch.groups!.end}`
177
+ const workerCtorReplacement = `new ${workerCtor}('${workerUrlPlaceholder}'${workerCtorMatch.groups!.end}`
189
178
  contents =
190
179
  contents.substring(0, workerCtorMatch.index + offset) +
191
180
  workerCtorReplacement +
@@ -199,6 +188,7 @@ export function workersPlugin(r: BuildRegistry): Plugin {
199
188
  r.addWorker({
200
189
  clientScript,
201
190
  workerEntryPoint,
191
+ workerCtor,
202
192
  workerUrl,
203
193
  workerUrlPlaceholder,
204
194
  })
package/lib/flags.ts CHANGED
@@ -1,84 +1,21 @@
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
- export type ProjectDirs = {
12
- buildRoot: string
13
- buildWatch: string
14
- buildDist: string
15
- pages: string
16
- pagesResolved: string
17
- projectResolved: string
18
- projectRootAbs: string
19
- public: string
20
- }
21
-
22
- export function resolveBuildFlags(): DankBuild {
23
- const flags: DankBuild = {
24
- dirs: defaultProjectDirs(cwd()),
25
- minify: willMinify(),
26
- production: isProductionBuild(),
27
- }
28
- return {
29
- get dirs(): ProjectDirs {
30
- return flags.dirs
31
- },
32
- get minify(): boolean {
33
- return flags.minify
34
- },
35
- get production(): boolean {
36
- return flags.production
37
- },
38
- }
39
- }
40
-
41
- export type DankServe = DankBuild & {
42
- dankPort: number
43
- esbuildPort: number
1
+ export type DankFlags = {
2
+ dankPort?: number
3
+ esbuildPort?: number
44
4
  logHttp: boolean
5
+ minify: boolean
45
6
  preview: boolean
7
+ production: boolean
46
8
  }
47
9
 
48
- export function resolveServeFlags(c: DankConfig): DankServe {
49
- const preview = isPreviewBuild()
50
- const flags: DankServe = {
51
- dirs: defaultProjectDirs(cwd()),
52
- dankPort: dankPort(c, preview),
53
- esbuildPort: esbuildPort(c),
10
+ export function resolveFlags(): Readonly<DankFlags> {
11
+ return Object.freeze({
12
+ dankPort: resolveDankPort(),
13
+ esbuildPort: resolveEsbuildPort(),
54
14
  logHttp: willLogHttp(),
55
15
  minify: willMinify(),
56
- preview,
16
+ preview: isPreviewBuild(),
57
17
  production: isProductionBuild(),
58
- }
59
- return {
60
- get dirs(): ProjectDirs {
61
- return flags.dirs
62
- },
63
- get dankPort(): number {
64
- return flags.dankPort
65
- },
66
- get esbuildPort(): number {
67
- return flags.esbuildPort
68
- },
69
- get logHttp(): boolean {
70
- return flags.logHttp
71
- },
72
- get minify(): boolean {
73
- return flags.minify
74
- },
75
- get preview(): boolean {
76
- return flags.preview
77
- },
78
- get production(): boolean {
79
- return flags.production
80
- },
81
- }
18
+ })
82
19
  }
83
20
 
84
21
  // `dank serve` will pre-bundle and use service worker
@@ -87,24 +24,21 @@ const isPreviewBuild = () =>
87
24
 
88
25
  // `dank build` will minify sources and append git release tag to build tag
89
26
  // `dank serve` will pre-bundle with service worker and minify
90
- export const isProductionBuild = () =>
27
+ const isProductionBuild = () =>
91
28
  process.env.PRODUCTION === 'true' || process.argv.includes('--production')
92
29
 
93
- // `dank serve` dank port for frontend webserver
94
- // alternate --preview port for service worker builds
95
- function dankPort(c: DankConfig, preview: boolean): number {
30
+ // `dank serve` port for frontend webserver
31
+ function resolveDankPort(): number | undefined {
96
32
  if (process.env.DANK_PORT?.length) {
97
33
  return parsePortEnvVar('DANK_PORT')
98
34
  }
99
- return preview ? c.previewPort || c.port || 4000 : c.port || 3000
100
35
  }
101
36
 
102
- // `dank serve` esbuild port for bundler integration
103
- function esbuildPort(c: DankConfig): number {
37
+ // `dank serve` port for esbuild bundler integration
38
+ function resolveEsbuildPort(): number | undefined {
104
39
  if (process.env.ESBUILD_PORT?.length) {
105
40
  return parsePortEnvVar('ESBUILD_PORT')
106
41
  }
107
- return c.esbuild?.port || 3995
108
42
  }
109
43
 
110
44
  function parsePortEnvVar(name: string): number {
@@ -116,46 +50,6 @@ function parsePortEnvVar(name: string): number {
116
50
  }
117
51
  }
118
52
 
119
- export function defaultProjectDirs(projectRootAbs: string): ProjectDirs {
120
- const pages = 'pages'
121
- const dirs: ProjectDirs = {
122
- buildRoot: 'build',
123
- buildDist: join('build', 'dist'),
124
- buildWatch: join('build', 'watch'),
125
- pages,
126
- pagesResolved: resolve(join(projectRootAbs, pages)),
127
- projectResolved: resolve(projectRootAbs),
128
- projectRootAbs,
129
- public: 'public',
130
- }
131
- return {
132
- get buildRoot(): string {
133
- return dirs.buildRoot
134
- },
135
- get buildDist(): string {
136
- return dirs.buildDist
137
- },
138
- get buildWatch(): string {
139
- return dirs.buildWatch
140
- },
141
- get pages(): string {
142
- return dirs.pages
143
- },
144
- get pagesResolved(): string {
145
- return dirs.pagesResolved
146
- },
147
- get projectResolved(): string {
148
- return dirs.projectResolved
149
- },
150
- get projectRootAbs(): string {
151
- return dirs.projectRootAbs
152
- },
153
- get public(): string {
154
- return dirs.public
155
- },
156
- }
157
- }
158
-
159
53
  const willMinify = () =>
160
54
  isProductionBuild() ||
161
55
  process.env.MINIFY === 'true' ||