@temir.ra/create-ts-lib 0.8.3 → 0.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Version 0
2
2
 
3
+ ## 0.9.0
4
+
5
+ 1. Switched to `esbuild` for bundling.
6
+ 2. Switched to `@types/node` for type definitions.
7
+ 3. Updated documentation to reflect the `esbuild` and `@types/node` changes.
8
+ 4. Synced with `template@0.2.0-pre.5` template.
9
+
10
+ ## 0.8.4
11
+
12
+ 1. Fixed CDN rewrite plugin filter.
13
+
3
14
  ## 0.8.3
4
15
 
5
16
  1. Updated buildinfo test wording to align with qualification strategies.
package/README.md CHANGED
@@ -27,25 +27,22 @@ A template for TypeScript libraries distributed via npm-compatible registries. P
27
27
 
28
28
  ```bash
29
29
  # placeholder:
30
- # <TEMPLATE_PACKAGE: @temir.ra/create-ts-lib
31
- # <TEMPLATE_NAME: @temir.ra/ts-lib
32
30
  # <NEW_PACKAGE: <NEW_PACKAGE>
33
- # is used as:
34
- # - the path where the package is created
35
- # - the "name" field in the generated package.json
36
- # <@_VERSION: <@_VERSION>
31
+ # <TEMPLATE_PACKAGE_NAME: @temir.ra/create-ts-lib
32
+ # <TEMPLATE_NAME: @temir.ra/ts-lib
33
+
34
+ mkdir -p <NEW_PACKAGE>
35
+ cd <NEW_PACKAGE>
37
36
 
38
- # pinned version
37
+ # print the latest version
39
38
  bun info "@temir.ra/create-ts-lib" version
40
- bun create --no-install --no-git "@temir.ra/ts-lib<@_VERSION>" <NEW_PACKAGE>
41
39
 
42
- # latest
43
- # clear the cache to pick up the latest version
44
- bun pm cache rm
45
- bun create --no-install --no-git "@temir.ra/ts-lib" <NEW_PACKAGE>
40
+ # create/update a package from the template in the current directory
41
+ # clear the package manager cache to pick up the latest version when not pinning a specific version
42
+ bun create --no-install --no-git "@temir.ra/ts-lib" .
43
+
44
+ # set metadata in package.json
46
45
 
47
- # templates only copy files, run install and any setup scripts manually
48
- cd <NEW_PACKAGE>
49
46
  bun install
50
47
  ```
51
48
 
@@ -53,22 +50,18 @@ bun install
53
50
 
54
51
  The following sections explain the configurations and conventions baked into the generated package. Useful when adapting it to fit specific needs.
55
52
 
56
- The central addition over [`create-workspace`](https://www.npmjs.com/package/@temir.ra/create-workspace) is a build pipeline for distributing the library. Two build strategies are supported:
53
+ The major addition compared to the [`workspace` template](https://www.npmjs.com/package/@temir.ra/create-workspace) is a build pipeline for distributing the library. Two build strategies are supported:
57
54
 
58
- - **Bundling** (`scripts/build-bundle.ts`) - generated by default; bundles the library to ESM and IIFE formats.
59
- - **TSC compilation** (`tsconfig.build.json` + `build:tsc`) - optional, not generated by default; compiles source files one-for-one to ESM JavaScript and declaration files.
55
+ - **Bundling** (`scripts/build-bundle.ts`) - bundles the library to ESM and IIFE formats using [`esbuild`](https://esbuild.github.io/).
56
+ - **TSC compilation** (`tsconfig.build.json` + `build:tsc`) - compiles the library to declaration files and ESM JavaScript using [`tsc`](https://www.typescriptlang.org/docs/handbook/compiler-options.html).
60
57
 
61
58
  Both strategies can be combined.
62
59
 
63
60
  ## `package.json`
64
61
 
65
- Selected fields are documented in the [`create-workspace` README](https://www.npmjs.com/package/@temir.ra/create-workspace#packagejson).
66
-
67
- See npmjs documentation on [package.json](https://docs.npmjs.com/cli/v11/configuring-npm/package-json) for detailed explanations of all fields.
62
+ Selected fields are documented in the [`workspace` template README](https://www.npmjs.com/package/@temir.ra/create-workspace#packagejson).
68
63
 
69
- The following lists the additions and overrides relative to [`create-workspace`](https://www.npmjs.com/package/@temir.ra/create-workspace#packagejson).
70
-
71
- The generated package is pre-configured with `build:bundle` only. See [TSC Compilation](#tsc-compilation) and [`"bin"` field](#bin-field-in-packagejson) for extending it.
64
+ The following fields are specific to this template:
72
65
 
73
66
  ```json
74
67
  {
@@ -150,8 +143,15 @@ The generated package is pre-configured with `build:bundle` only. See [TSC Compi
150
143
  "build:tsc": "tsc --project tsconfig.build.json",
151
144
 
152
145
  // bundles the CLI into a single file for distribution; requires the "bin" field to be set to the bundled output
153
- "build:cli-bundle": "bun build src/cli.ts --entry-naming \"[dir]/[name].bundle.[ext]\" --outdir dist --target node --format esm --minify --sourcemap=external"
146
+ "build:cli-bundle": "esbuild src/cli.ts --outdir=dist --entry-names=[dir]/[name].bundle --platform=node --format=esm --bundle --minify --sourcemap=external"
147
+
148
+ },
154
149
 
150
+ "devDependencies": {
151
+ "@types/bun": "latest",
152
+ "@types/node": "latest",
153
+ "esbuild": "latest",
154
+ "typescript": "^6.0.3"
155
155
  }
156
156
 
157
157
  }
@@ -159,9 +159,11 @@ The generated package is pre-configured with `build:bundle` only. See [TSC Compi
159
159
 
160
160
  It is highly recommended to include `tests/` in the `files` field. Note, that `tests/` is not included by default in the generated `package.json`.
161
161
 
162
+ `"@types/bun": "latest"` is required to run tests.
163
+
162
164
  ## Script `scripts/build-bundle.ts`
163
165
 
164
- Bundles the library to ESM and IIFE formats. Entry points are resolved from the `entrypoint` condition in the `exports` field of `package.json`. Note, the `entrypoint` condition is a custom, non-standard export condition used solely for build tooling.
166
+ Bundles the library to ESM and IIFE formats using `esbuild`. Entry points are resolved from the `entrypoint` condition in the `exports` field of `package.json`. Note, the `entrypoint` condition is a custom, non-standard export condition used solely for build tooling.
165
167
 
166
168
  Import specifiers can be rewritten to CDN URLs via `scripts/cdn-rewrite-map.json`.
167
169
 
@@ -181,7 +183,7 @@ Maps import specifiers to CDN URLs. During bundling, matching specifiers in sour
181
183
 
182
184
  `tsconfig.json` provided by [`workspace` template](https://www.npmjs.com/package/@temir.ra/create-workspace) is intended for development only. `tsconfig.build.json` extends it with settings for compiling the source files for distribution.
183
185
 
184
- See [`create-workspace` README](https://www.npmjs.com/package/@temir.ra/create-workspace#tsconfigjson) and [tsconfig.json](https://www.typescriptlang.org/tsconfig) for detailed explanations of all options.
186
+ See [`workspace` template README](https://www.npmjs.com/package/@temir.ra/create-workspace#tsconfigjson) and [tsconfig.json](https://www.typescriptlang.org/tsconfig) for detailed explanations of all options.
185
187
 
186
188
  ```json
187
189
  {
@@ -360,6 +362,8 @@ const asset = await readFile(assetPath);
360
362
 
361
363
  The generated `./src/constants.ts` scaffolds the isomorphic approach and `./constants` is configured as an additional entry point in `package.json` for direct imports:
362
364
 
365
+ ⚠️ Beware that `import.meta.url` is replaced by `document.currentScript.src` during bundling in IIFE format since it is available only under ESM (research "import.meta vs document.currentScript.src").
366
+
363
367
  ```json
364
368
  {
365
369
  // ... ,
package/buildinfo.txt CHANGED
@@ -1 +1 @@
1
- 0.8.3+e12bddf
1
+ 0.9.0+1a97fac
@@ -1,4 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{cpSync as a,readFileSync as g,renameSync as U,writeFileSync as d}from"fs";import{resolve as r}from"path";var n=new URL("../",import.meta.url),i=new URL("buildinfo.txt",n),u=new URL("dist/",n),l=new URL("template/",n),m=new URL("CHANGELOG.md",n),p=new URL("README.md",n);try{let t=process.argv[2];if(!t)throw Error('First argument must be the package name (e.g. "my-package" or "@my-scope/my-package").');let o=t.replace(/\\/g,"/"),e=r(process.cwd(),o);a(l,e,{recursive:!0}),a(m,r(e,"CHANGELOG-template.md")),a(i,r(e,"buildinfo-template.txt")),a(p,r(e,"README-template.md"));let c=r(e,"package.json"),s=JSON.parse(g(c,"utf-8"));s.name=o,d(c,JSON.stringify(s,null,2)),U(r(e,"gitignore"),r(e,".gitignore")),console.log(`Template has been successfully instantiated at '${e}' with package name '${o}'.`)}catch(t){let o=t instanceof Error?t:Error(String(t));console.error("Error:",o.message),process.exit(1)}
3
-
4
- //# debugId=EF224A42FEFD5AB864756E2164756E21
2
+ import{cpSync as a,readFileSync as g,renameSync as U,writeFileSync as d}from"fs";import{resolve as r}from"path";var n=new URL("../",import.meta.url),i=new URL("buildinfo.txt",n),u=new URL("dist/",n),l=new URL("template/",n),m=new URL("CHANGELOG.md",n),p=new URL("README.md",n);try{let t=process.argv[2];if(!t)throw new Error('First argument must be the package name (e.g. "my-package" or "@my-scope/my-package").');let o=t.replace(/\\/g,"/"),e=r(process.cwd(),o);a(l,e,{recursive:!0}),a(m,r(e,"CHANGELOG-template.md")),a(i,r(e,"buildinfo-template.txt")),a(p,r(e,"README-template.md"));let c=r(e,"package.json"),s=JSON.parse(g(c,"utf-8"));s.name=o,d(c,JSON.stringify(s,null,2)),U(r(e,"gitignore"),r(e,".gitignore")),console.log(`Template has been successfully instantiated at '${e}' with package name '${o}'.`)}catch(t){let o=t instanceof Error?t:new Error(String(t));console.error("Error:",o.message),process.exit(1)}
@@ -1,11 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["..\\src\\cli.ts", "..\\src\\constants.ts"],
4
- "sourcesContent": [
5
- "#!/usr/bin/env node\r\n\r\nimport { cpSync, readFileSync, renameSync, writeFileSync } from 'fs';\r\nimport { resolve } from 'path';\r\nimport {\r\n templateUrl,\r\n changelogUrl,\r\n buildinfoUrl,\r\n readmeUrl\r\n} from './constants.js';\r\n\r\n\r\ntry {\r\n\r\n const packageNameArgument = process.argv[2];\r\n if (!packageNameArgument)\r\n throw new Error('First argument must be the package name (e.g. \"my-package\" or \"@my-scope/my-package\").');\r\n const packageName = packageNameArgument.replace(/\\\\/g, '/');\r\n\r\n const destinationPath = resolve(process.cwd(), packageName);\r\n\r\n cpSync(templateUrl, destinationPath, { recursive: true });\r\n cpSync(changelogUrl, resolve(destinationPath, 'CHANGELOG-template.md'));\r\n cpSync(buildinfoUrl, resolve(destinationPath, 'buildinfo-template.txt'));\r\n cpSync(readmeUrl, resolve(destinationPath, 'README-template.md'));\r\n\r\n const packageJsonPath = resolve(destinationPath, 'package.json');\r\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\r\n packageJson.name = packageName;\r\n writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));\r\n\r\n renameSync(resolve(destinationPath, 'gitignore'), resolve(destinationPath, '.gitignore'));\r\n\r\n console.log(`Template has been successfully instantiated at '${destinationPath}' with package name '${packageName}'.`);\r\n\r\n}\r\ncatch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n console.error('Error:', err.message);\r\n process.exit(1);\r\n}\r\n",
6
- "export const packageUrl: URL = new URL('../', import.meta.url);\r\nexport const buildinfoUrl: URL = new URL('buildinfo.txt', packageUrl);\r\nexport const distUrl: URL = new URL('dist/', packageUrl);\r\n\r\nexport const templateUrl: URL = new URL('template/', packageUrl);\r\nexport const changelogUrl: URL = new URL('CHANGELOG.md', packageUrl);\r\nexport const readmeUrl: URL = new URL('README.md', packageUrl);\r\n"
7
- ],
8
- "mappings": ";AAEA,iBAAS,kBAAQ,gBAAc,mBAAY,WAC3C,kBAAS,aCHF,IAAM,EAAkB,IAAI,IAAI,MAAO,YAAY,GAAG,EAChD,EAAoB,IAAI,IAAI,gBAAiB,CAAU,EACvD,EAAe,IAAI,IAAI,QAAS,CAAU,EAE1C,EAAmB,IAAI,IAAI,YAAa,CAAU,EAClD,EAAoB,IAAI,IAAI,eAAgB,CAAU,EACtD,EAAiB,IAAI,IAAI,YAAa,CAAU,EDM7D,GAAI,CAEA,IAAM,EAAsB,QAAQ,KAAK,GACzC,GAAI,CAAC,EACD,MAAU,MAAM,wFAAwF,EAC5G,IAAM,EAAc,EAAoB,QAAQ,MAAO,GAAG,EAEpD,EAAkB,EAAQ,QAAQ,IAAI,EAAG,CAAW,EAE1D,EAAO,EAAa,EAAiB,CAAE,UAAW,EAAK,CAAC,EACxD,EAAO,EAAc,EAAQ,EAAiB,uBAAuB,CAAC,EACtE,EAAO,EAAc,EAAQ,EAAiB,wBAAwB,CAAC,EACvE,EAAO,EAAW,EAAQ,EAAiB,oBAAoB,CAAC,EAEhE,IAAM,EAAkB,EAAQ,EAAiB,cAAc,EACzD,EAAc,KAAK,MAAM,EAAa,EAAiB,OAAO,CAAC,EACrE,EAAY,KAAO,EACnB,EAAc,EAAiB,KAAK,UAAU,EAAa,KAAM,CAAC,CAAC,EAEnE,EAAW,EAAQ,EAAiB,WAAW,EAAG,EAAQ,EAAiB,YAAY,CAAC,EAExF,QAAQ,IAAI,mDAAmD,yBAAuC,KAAe,EAGzH,MAAO,EAAO,CACV,IAAM,EAAM,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,EACpE,QAAQ,MAAM,SAAU,EAAI,OAAO,EACnC,QAAQ,KAAK,CAAC",
9
- "debugId": "EF224A42FEFD5AB864756E2164756E21",
10
- "names": []
11
- }
3
+ "sources": ["../src/cli.ts", "../src/constants.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\r\n\r\nimport { cpSync, readFileSync, renameSync, writeFileSync } from 'fs';\r\nimport { resolve } from 'path';\r\nimport {\r\n templateUrl,\r\n changelogUrl,\r\n buildinfoUrl,\r\n readmeUrl\r\n} from './constants.js';\r\n\r\n\r\ntry {\r\n\r\n const packageNameArgument = process.argv[2];\r\n if (!packageNameArgument)\r\n throw new Error('First argument must be the package name (e.g. \"my-package\" or \"@my-scope/my-package\").');\r\n const packageName = packageNameArgument.replace(/\\\\/g, '/');\r\n\r\n const destinationPath = resolve(process.cwd(), packageName);\r\n\r\n cpSync(templateUrl, destinationPath, { recursive: true });\r\n cpSync(changelogUrl, resolve(destinationPath, 'CHANGELOG-template.md'));\r\n cpSync(buildinfoUrl, resolve(destinationPath, 'buildinfo-template.txt'));\r\n cpSync(readmeUrl, resolve(destinationPath, 'README-template.md'));\r\n\r\n const packageJsonPath = resolve(destinationPath, 'package.json');\r\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\r\n packageJson.name = packageName;\r\n writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));\r\n\r\n renameSync(resolve(destinationPath, 'gitignore'), resolve(destinationPath, '.gitignore'));\r\n\r\n console.log(`Template has been successfully instantiated at '${destinationPath}' with package name '${packageName}'.`);\r\n\r\n}\r\ncatch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n console.error('Error:', err.message);\r\n process.exit(1);\r\n}\r\n", "export const packageUrl: URL = new URL('../', import.meta.url);\r\nexport const buildinfoUrl: URL = new URL('buildinfo.txt', packageUrl);\r\nexport const distUrl: URL = new URL('dist/', packageUrl);\r\n\r\nexport const templateUrl: URL = new URL('template/', packageUrl);\r\nexport const changelogUrl: URL = new URL('CHANGELOG.md', packageUrl);\r\nexport const readmeUrl: URL = new URL('README.md', packageUrl);\r\n"],
5
+ "mappings": ";AAEA,OAAS,UAAAA,EAAQ,gBAAAC,EAAc,cAAAC,EAAY,iBAAAC,MAAqB,KAChE,OAAS,WAAAC,MAAe,OCHjB,IAAMC,EAAkB,IAAI,IAAI,MAAO,YAAY,GAAG,EAChDC,EAAoB,IAAI,IAAI,gBAAiBD,CAAU,EACvDE,EAAe,IAAI,IAAI,QAASF,CAAU,EAE1CG,EAAmB,IAAI,IAAI,YAAaH,CAAU,EAClDI,EAAoB,IAAI,IAAI,eAAgBJ,CAAU,EACtDK,EAAiB,IAAI,IAAI,YAAaL,CAAU,EDM7D,GAAI,CAEA,IAAMM,EAAsB,QAAQ,KAAK,CAAC,EAC1C,GAAI,CAACA,EACD,MAAM,IAAI,MAAM,wFAAwF,EAC5G,IAAMC,EAAcD,EAAoB,QAAQ,MAAO,GAAG,EAEpDE,EAAkBC,EAAQ,QAAQ,IAAI,EAAGF,CAAW,EAE1DG,EAAOC,EAAaH,EAAiB,CAAE,UAAW,EAAK,CAAC,EACxDE,EAAOE,EAAcH,EAAQD,EAAiB,uBAAuB,CAAC,EACtEE,EAAOG,EAAcJ,EAAQD,EAAiB,wBAAwB,CAAC,EACvEE,EAAOI,EAAWL,EAAQD,EAAiB,oBAAoB,CAAC,EAEhE,IAAMO,EAAkBN,EAAQD,EAAiB,cAAc,EACzDQ,EAAc,KAAK,MAAMC,EAAaF,EAAiB,OAAO,CAAC,EACrEC,EAAY,KAAOT,EACnBW,EAAcH,EAAiB,KAAK,UAAUC,EAAa,KAAM,CAAC,CAAC,EAEnEG,EAAWV,EAAQD,EAAiB,WAAW,EAAGC,EAAQD,EAAiB,YAAY,CAAC,EAExF,QAAQ,IAAI,mDAAmDA,CAAe,wBAAwBD,CAAW,IAAI,CAEzH,OACOa,EAAO,CACV,IAAMC,EAAMD,aAAiB,MAAQA,EAAQ,IAAI,MAAM,OAAOA,CAAK,CAAC,EACpE,QAAQ,MAAM,SAAUC,EAAI,OAAO,EACnC,QAAQ,KAAK,CAAC,CAClB",
6
+ "names": ["cpSync", "readFileSync", "renameSync", "writeFileSync", "resolve", "packageUrl", "buildinfoUrl", "distUrl", "templateUrl", "changelogUrl", "readmeUrl", "packageNameArgument", "packageName", "destinationPath", "resolve", "cpSync", "templateUrl", "changelogUrl", "buildinfoUrl", "readmeUrl", "packageJsonPath", "packageJson", "readFileSync", "writeFileSync", "renameSync", "error", "err"]
7
+ }
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@temir.ra/create-ts-lib",
3
- "version": "0.8.3",
3
+ "version": "0.9.0",
4
4
  "description": "A template for a distributable TypeScript library package.",
5
5
  "author": "temir.ra",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "typescript",
9
- "bun",
10
9
  "template",
11
10
  "library"
12
11
  ],
@@ -39,10 +38,12 @@
39
38
  "tests": "bun test",
40
39
  "prebuild": "bun run buildinfo",
41
40
  "build": "bun run build:cli-bundle",
42
- "build:cli-bundle": "bun build src/cli.ts --entry-naming \"[dir]/[name].bundle.[ext]\" --outdir dist --target node --format esm --minify --sourcemap=external"
41
+ "build:cli-bundle": "esbuild src/cli.ts --outdir=dist --entry-names=[dir]/[name].bundle --platform=node --format=esm --bundle --minify --sourcemap=external"
43
42
  },
44
43
  "devDependencies": {
45
44
  "@types/bun": "latest",
45
+ "@types/node": "latest",
46
+ "esbuild": "latest",
46
47
  "typescript": "^6.0.3"
47
48
  }
48
49
  }
@@ -1,5 +1,15 @@
1
1
  import { readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import {
4
+ build,
5
+ type BuildFailure,
6
+ type OnResolveArgs,
7
+ type OnResolveResult,
8
+ type Plugin as EsbuildPlugin,
9
+ type PluginBuild,
10
+ type BuildOptions,
11
+ type BuildResult
12
+ } from 'esbuild';
3
13
 
4
14
  import CDN_REWRITE_MAP from './cdn-rewrite-map.json';
5
15
 
@@ -11,17 +21,15 @@ interface ExportConditions {
11
21
  browser?: string;
12
22
  import?: string;
13
23
  }
14
-
15
- interface DependencyMap {
16
- [packageName: string]: string;
24
+ interface Dependency {
25
+ [key: string]: string;
17
26
  }
18
-
19
27
  interface PackageManifest {
20
28
  version: string;
21
29
  exports?: Record<string, ExportConditions>;
22
- dependencies?: DependencyMap;
23
- devDependencies?: DependencyMap;
24
- peerDependencies?: DependencyMap;
30
+ dependencies?: Dependency;
31
+ devDependencies?: Dependency;
32
+ peerDependencies?: Dependency;
25
33
  }
26
34
 
27
35
  function getManifest(packageIdentifier?: string): PackageManifest {
@@ -32,6 +40,7 @@ function getManifest(packageIdentifier?: string): PackageManifest {
32
40
  ;
33
41
 
34
42
  let manifest: PackageManifest;
43
+
35
44
  try {
36
45
  manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
37
46
  } catch {
@@ -41,24 +50,22 @@ function getManifest(packageIdentifier?: string): PackageManifest {
41
50
  return manifest;
42
51
 
43
52
  }
44
-
45
53
  function getManifestEntrypoints(packageManifest: PackageManifest): string[] {
46
54
 
47
55
  const exports = packageManifest.exports;
48
56
  if (!exports)
49
- throw new Error(`[scripts/build-bundle.ts] No 'exports' field found in the given package manifest.`);
57
+ throw new Error(`[scripts/build-bundle.ts] Failed to read entrypoints from package manifest: 'exports' field is missing.`);
50
58
 
51
59
  const entrypoints = Object.entries(exports)
52
60
  .map(([key, conditions]) => {
53
61
  if (!conditions.entrypoint)
54
- throw new Error(`[scripts/build-bundle.ts] Export '${key}' does not have an 'entrypoint' condition.`);
62
+ throw new Error(`[scripts/build-bundle.ts] Failed to read entrypoints from package manifest: 'entrypoint' condition is missing for export '${key}'.`);
55
63
  return conditions.entrypoint;
56
64
  });
57
65
 
58
66
  return entrypoints;
59
67
 
60
68
  }
61
-
62
69
  function getPackageVersion(manifest: PackageManifest, packageIdentifier?: string): string {
63
70
 
64
71
  let version: string | undefined;
@@ -72,12 +79,12 @@ function getPackageVersion(manifest: PackageManifest, packageIdentifier?: string
72
79
 
73
80
  version = dependencies[packageIdentifier];
74
81
  if (!version)
75
- throw new Error(`[scripts/build-bundle.ts] Package '${packageIdentifier}' is not listed in dependencies.`);
82
+ throw new Error(`[scripts/build-bundle.ts] Failed to read version for package '${packageIdentifier}': package is not listed in dependencies.`);
76
83
 
77
84
  }
78
85
  else {
79
86
  if (!manifest.version)
80
- throw new Error('[scripts/build-bundle.ts] Package manifest does not contain a version field.');
87
+ throw new Error('[scripts/build-bundle.ts] Failed to read package version from manifest: version field is missing.');
81
88
  version = manifest.version;
82
89
  }
83
90
 
@@ -85,27 +92,54 @@ function getPackageVersion(manifest: PackageManifest, packageIdentifier?: string
85
92
 
86
93
  }
87
94
 
88
- function resolveCdnUrl(importSpecifier: string, urlTemplate: string): string {
89
- const manifest = getManifest();
90
- const version = getPackageVersion(manifest, importSpecifier);
91
- return urlTemplate.replace('<VERSION>', version);
95
+ function resolveVersionInCdnUrl(importSpecifier: string, cdnUrl: string): string {
96
+ if (cdnUrl.includes('<VERSION>')) {
97
+ const manifest = getManifest();
98
+ const version = getPackageVersion(manifest, importSpecifier);
99
+ let resolvedVersion = version;
100
+ if (resolvedVersion.startsWith('workspace:')) resolvedVersion = resolvedVersion.replace(/^workspace:/, '');
101
+ if (resolvedVersion.startsWith('^') || resolvedVersion.startsWith('~')) resolvedVersion = resolvedVersion.substring(1);
102
+ if (resolvedVersion.length === 0)
103
+ throw new Error(`[scripts/build-bundle.ts] Failed to resolve version for package '${importSpecifier}': resolved version is empty.`);
104
+ return cdnUrl.replace('<VERSION>', resolvedVersion);
105
+ }
106
+ return cdnUrl;
92
107
  }
93
108
 
94
- const cdnRewritePlugin = {
109
+ const esbuildLog: EsbuildPlugin = {
110
+ name: 'esbuild-log',
111
+ setup(build: PluginBuild) {
112
+ const format = build.initialOptions.format || 'unknown';
113
+ build.onStart(() => {
114
+ console.log(`[esbuild-log plugin] ${format} build started...`);
115
+ });
116
+ build.onEnd((result: BuildResult<BuildOptions>) => {
117
+ if (result.errors.length > 0) {
118
+ console.error(`[esbuild-log plugin] ${format} build failed.`);
119
+ } else {
120
+ console.log(`[esbuild-log plugin] ${format} build finished.`);
121
+ }
122
+ });
123
+ }
124
+ }
125
+ const cdnRewrite: EsbuildPlugin = {
95
126
  name: 'cdn-rewrite',
96
- setup(build: any) {
127
+ setup(build: PluginBuild) {
97
128
 
98
- const resolved = new Map<string, string>();
99
129
  for (const [importSpecifier, urlTemplate] of Object.entries(CDN_REWRITE_MAP) as [string, string][]) {
100
- const url = resolveCdnUrl(importSpecifier, urlTemplate);
101
- resolved.set(importSpecifier, url);
102
- console.log(`[cdn-rewrite] '${importSpecifier}' → '${url}'`);
103
- }
104
130
 
105
- build.onResolve({ filter: /.*/ }, (args: any) => {
106
- const url = resolved.get(args.path);
107
- if (url) return { path: url, external: true };
108
- });
131
+ const url = resolveVersionInCdnUrl(importSpecifier, urlTemplate);
132
+ console.log(`[cdn-rewrite plugin] '${importSpecifier}' → '${url}'`);
133
+
134
+ const escapedSpecifier = importSpecifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
135
+ build.onResolve(
136
+ { filter: new RegExp(`^${escapedSpecifier}$`) },
137
+ (args: OnResolveArgs): OnResolveResult | undefined => {
138
+ return { path: url, external: true };
139
+ }
140
+ );
141
+
142
+ }
109
143
 
110
144
  },
111
145
  };
@@ -114,50 +148,44 @@ const cdnRewritePlugin = {
114
148
  const entrypoints = getManifestEntrypoints(getManifest());
115
149
  console.log('[scripts/build-bundle.ts] Entrypoints:', entrypoints);
116
150
 
117
- const omitIife = process.argv.includes('--omit-iife');
118
-
119
- let buildResult;
120
-
121
- console.log('[scripts/build-bundle.ts] Starting ESM bundle build...');
122
- buildResult = await Bun.build({
123
- entrypoints,
124
- outdir: 'dist',
125
- naming: '[dir]/[name].bundle.[ext]',
126
- target: 'browser',
151
+ const buildOptions: BuildOptions = {
152
+ entryPoints: entrypoints,
153
+ outdir: 'dist/',
154
+ entryNames: '[dir]/[name].bundle',
155
+ platform: 'browser',
127
156
  format: 'esm',
157
+ bundle: true,
128
158
  minify: true,
129
159
  sourcemap: 'external',
130
- plugins: [cdnRewritePlugin],
131
- });
160
+ plugins: [esbuildLog, cdnRewrite],
132
161
 
133
- if (!buildResult.success) {
162
+ };
163
+
164
+ try {
165
+ await build(buildOptions);
166
+ } catch (error) {
134
167
  console.error('[scripts/build-bundle.ts] Build failed:');
135
- for (const message of buildResult.logs) {
168
+ for (const message of (error as BuildFailure).errors) {
136
169
  console.error(message);
137
170
  }
138
171
  process.exit(1);
139
172
  }
140
- console.log('[scripts/build-bundle.ts] ESM bundle build completed successfully.');
141
173
 
174
+ const omitIife = process.argv.includes('--omit-iife');
142
175
  if (!omitIife) {
143
- console.log('[scripts/build-bundle.ts] Starting IIFE bundle build...');
144
- buildResult = await Bun.build({
145
- entrypoints,
146
- outdir: 'dist',
147
- naming: '[dir]/[name].iife.[ext]',
148
- target: 'browser',
149
- format: 'iife',
150
- minify: true,
151
- sourcemap: 'external',
152
- plugins: [cdnRewritePlugin],
153
- });
154
-
155
- if (!buildResult.success) {
176
+ try {
177
+ await build({
178
+ ...buildOptions,
179
+ entryNames: '[dir]/[name].iife.bundle',
180
+ format: 'iife',
181
+ banner: { js: 'var __importMetaUrl = document.currentScript && document.currentScript.src || \'\';' },
182
+ define: { 'import.meta.url': '__importMetaUrl' },
183
+ });
184
+ } catch (error) {
156
185
  console.error('[scripts/build-bundle.ts] Build failed:');
157
- for (const message of buildResult.logs) {
186
+ for (const message of (error as BuildFailure).errors) {
158
187
  console.error(message);
159
188
  }
160
189
  process.exit(1);
161
190
  }
162
- console.log('[scripts/build-bundle.ts] IIFE bundle build completed successfully.');
163
191
  }
@@ -5,8 +5,7 @@
5
5
  "author": "",
6
6
  "license": "",
7
7
  "keywords": [
8
- "typescript",
9
- "bun"
8
+ "typescript"
10
9
  ],
11
10
  "repository": {
12
11
  "type": "git",
@@ -54,6 +53,8 @@
54
53
  },
55
54
  "devDependencies": {
56
55
  "@types/bun": "latest",
56
+ "@types/node": "latest",
57
+ "esbuild": "latest",
57
58
  "typescript": "^6.0.3"
58
59
  }
59
60
  }
@@ -1,5 +1,15 @@
1
1
  import { readFileSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import {
4
+ build,
5
+ type BuildFailure,
6
+ type OnResolveArgs,
7
+ type OnResolveResult,
8
+ type Plugin as EsbuildPlugin,
9
+ type PluginBuild,
10
+ type BuildOptions,
11
+ type BuildResult
12
+ } from 'esbuild';
3
13
 
4
14
  import CDN_REWRITE_MAP from './cdn-rewrite-map.json';
5
15
 
@@ -11,17 +21,15 @@ interface ExportConditions {
11
21
  browser?: string;
12
22
  import?: string;
13
23
  }
14
-
15
- interface DependencyMap {
16
- [packageName: string]: string;
24
+ interface Dependency {
25
+ [key: string]: string;
17
26
  }
18
-
19
27
  interface PackageManifest {
20
28
  version: string;
21
29
  exports?: Record<string, ExportConditions>;
22
- dependencies?: DependencyMap;
23
- devDependencies?: DependencyMap;
24
- peerDependencies?: DependencyMap;
30
+ dependencies?: Dependency;
31
+ devDependencies?: Dependency;
32
+ peerDependencies?: Dependency;
25
33
  }
26
34
 
27
35
  function getManifest(packageIdentifier?: string): PackageManifest {
@@ -32,6 +40,7 @@ function getManifest(packageIdentifier?: string): PackageManifest {
32
40
  ;
33
41
 
34
42
  let manifest: PackageManifest;
43
+
35
44
  try {
36
45
  manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
37
46
  } catch {
@@ -41,24 +50,22 @@ function getManifest(packageIdentifier?: string): PackageManifest {
41
50
  return manifest;
42
51
 
43
52
  }
44
-
45
53
  function getManifestEntrypoints(packageManifest: PackageManifest): string[] {
46
54
 
47
55
  const exports = packageManifest.exports;
48
56
  if (!exports)
49
- throw new Error(`[scripts/build-bundle.ts] No 'exports' field found in the given package manifest.`);
57
+ throw new Error(`[scripts/build-bundle.ts] Failed to read entrypoints from package manifest: 'exports' field is missing.`);
50
58
 
51
59
  const entrypoints = Object.entries(exports)
52
60
  .map(([key, conditions]) => {
53
61
  if (!conditions.entrypoint)
54
- throw new Error(`[scripts/build-bundle.ts] Export '${key}' does not have an 'entrypoint' condition.`);
62
+ throw new Error(`[scripts/build-bundle.ts] Failed to read entrypoints from package manifest: 'entrypoint' condition is missing for export '${key}'.`);
55
63
  return conditions.entrypoint;
56
64
  });
57
65
 
58
66
  return entrypoints;
59
67
 
60
68
  }
61
-
62
69
  function getPackageVersion(manifest: PackageManifest, packageIdentifier?: string): string {
63
70
 
64
71
  let version: string | undefined;
@@ -72,12 +79,12 @@ function getPackageVersion(manifest: PackageManifest, packageIdentifier?: string
72
79
 
73
80
  version = dependencies[packageIdentifier];
74
81
  if (!version)
75
- throw new Error(`[scripts/build-bundle.ts] Package '${packageIdentifier}' is not listed in dependencies.`);
82
+ throw new Error(`[scripts/build-bundle.ts] Failed to read version for package '${packageIdentifier}': package is not listed in dependencies.`);
76
83
 
77
84
  }
78
85
  else {
79
86
  if (!manifest.version)
80
- throw new Error('[scripts/build-bundle.ts] Package manifest does not contain a version field.');
87
+ throw new Error('[scripts/build-bundle.ts] Failed to read package version from manifest: version field is missing.');
81
88
  version = manifest.version;
82
89
  }
83
90
 
@@ -85,27 +92,54 @@ function getPackageVersion(manifest: PackageManifest, packageIdentifier?: string
85
92
 
86
93
  }
87
94
 
88
- function resolveCdnUrl(importSpecifier: string, urlTemplate: string): string {
89
- const manifest = getManifest();
90
- const version = getPackageVersion(manifest, importSpecifier);
91
- return urlTemplate.replace('<VERSION>', version);
95
+ function resolveVersionInCdnUrl(importSpecifier: string, cdnUrl: string): string {
96
+ if (cdnUrl.includes('<VERSION>')) {
97
+ const manifest = getManifest();
98
+ const version = getPackageVersion(manifest, importSpecifier);
99
+ let resolvedVersion = version;
100
+ if (resolvedVersion.startsWith('workspace:')) resolvedVersion = resolvedVersion.replace(/^workspace:/, '');
101
+ if (resolvedVersion.startsWith('^') || resolvedVersion.startsWith('~')) resolvedVersion = resolvedVersion.substring(1);
102
+ if (resolvedVersion.length === 0)
103
+ throw new Error(`[scripts/build-bundle.ts] Failed to resolve version for package '${importSpecifier}': resolved version is empty.`);
104
+ return cdnUrl.replace('<VERSION>', resolvedVersion);
105
+ }
106
+ return cdnUrl;
92
107
  }
93
108
 
94
- const cdnRewritePlugin = {
109
+ const esbuildLog: EsbuildPlugin = {
110
+ name: 'esbuild-log',
111
+ setup(build: PluginBuild) {
112
+ const format = build.initialOptions.format || 'unknown';
113
+ build.onStart(() => {
114
+ console.log(`[esbuild-log plugin] ${format} build started...`);
115
+ });
116
+ build.onEnd((result: BuildResult<BuildOptions>) => {
117
+ if (result.errors.length > 0) {
118
+ console.error(`[esbuild-log plugin] ${format} build failed.`);
119
+ } else {
120
+ console.log(`[esbuild-log plugin] ${format} build finished.`);
121
+ }
122
+ });
123
+ }
124
+ }
125
+ const cdnRewrite: EsbuildPlugin = {
95
126
  name: 'cdn-rewrite',
96
- setup(build: any) {
127
+ setup(build: PluginBuild) {
97
128
 
98
- const resolved = new Map<string, string>();
99
129
  for (const [importSpecifier, urlTemplate] of Object.entries(CDN_REWRITE_MAP) as [string, string][]) {
100
- const url = resolveCdnUrl(importSpecifier, urlTemplate);
101
- resolved.set(importSpecifier, url);
102
- console.log(`[cdn-rewrite] '${importSpecifier}' → '${url}'`);
103
- }
104
130
 
105
- build.onResolve({ filter: /.*/ }, (args: any) => {
106
- const url = resolved.get(args.path);
107
- if (url) return { path: url, external: true };
108
- });
131
+ const url = resolveVersionInCdnUrl(importSpecifier, urlTemplate);
132
+ console.log(`[cdn-rewrite plugin] '${importSpecifier}' → '${url}'`);
133
+
134
+ const escapedSpecifier = importSpecifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
135
+ build.onResolve(
136
+ { filter: new RegExp(`^${escapedSpecifier}$`) },
137
+ (args: OnResolveArgs): OnResolveResult | undefined => {
138
+ return { path: url, external: true };
139
+ }
140
+ );
141
+
142
+ }
109
143
 
110
144
  },
111
145
  };
@@ -114,50 +148,44 @@ const cdnRewritePlugin = {
114
148
  const entrypoints = getManifestEntrypoints(getManifest());
115
149
  console.log('[scripts/build-bundle.ts] Entrypoints:', entrypoints);
116
150
 
117
- const omitIife = process.argv.includes('--omit-iife');
118
-
119
- let buildResult;
120
-
121
- console.log('[scripts/build-bundle.ts] Starting ESM bundle build...');
122
- buildResult = await Bun.build({
123
- entrypoints,
124
- outdir: 'dist',
125
- naming: '[dir]/[name].bundle.[ext]',
126
- target: 'browser',
151
+ const buildOptions: BuildOptions = {
152
+ entryPoints: entrypoints,
153
+ outdir: 'dist/',
154
+ entryNames: '[dir]/[name].bundle',
155
+ platform: 'browser',
127
156
  format: 'esm',
157
+ bundle: true,
128
158
  minify: true,
129
159
  sourcemap: 'external',
130
- plugins: [cdnRewritePlugin],
131
- });
160
+ plugins: [esbuildLog, cdnRewrite],
132
161
 
133
- if (!buildResult.success) {
162
+ };
163
+
164
+ try {
165
+ await build(buildOptions);
166
+ } catch (error) {
134
167
  console.error('[scripts/build-bundle.ts] Build failed:');
135
- for (const message of buildResult.logs) {
168
+ for (const message of (error as BuildFailure).errors) {
136
169
  console.error(message);
137
170
  }
138
171
  process.exit(1);
139
172
  }
140
- console.log('[scripts/build-bundle.ts] ESM bundle build completed successfully.');
141
173
 
174
+ const omitIife = process.argv.includes('--omit-iife');
142
175
  if (!omitIife) {
143
- console.log('[scripts/build-bundle.ts] Starting IIFE bundle build...');
144
- buildResult = await Bun.build({
145
- entrypoints,
146
- outdir: 'dist',
147
- naming: '[dir]/[name].iife.[ext]',
148
- target: 'browser',
149
- format: 'iife',
150
- minify: true,
151
- sourcemap: 'external',
152
- plugins: [cdnRewritePlugin],
153
- });
154
-
155
- if (!buildResult.success) {
176
+ try {
177
+ await build({
178
+ ...buildOptions,
179
+ entryNames: '[dir]/[name].iife.bundle',
180
+ format: 'iife',
181
+ banner: { js: 'var __importMetaUrl = document.currentScript && document.currentScript.src || \'\';' },
182
+ define: { 'import.meta.url': '__importMetaUrl' },
183
+ });
184
+ } catch (error) {
156
185
  console.error('[scripts/build-bundle.ts] Build failed:');
157
- for (const message of buildResult.logs) {
186
+ for (const message of (error as BuildFailure).errors) {
158
187
  console.error(message);
159
188
  }
160
189
  process.exit(1);
161
190
  }
162
- console.log('[scripts/build-bundle.ts] IIFE bundle build completed successfully.');
163
191
  }
@@ -1,3 +1,4 @@
1
+ /// <reference types="bun" />
1
2
  import { describe, it, expect } from 'bun:test';
2
3
  import { readFileSync } from 'fs';
3
4
  import { fileURLToPath } from 'url';
@@ -17,7 +17,7 @@
17
17
  "composite": true,
18
18
  "skipLibCheck": true,
19
19
  "types": [
20
- "bun"
20
+ "node"
21
21
  ],
22
22
  "forceConsistentCasingInFileNames": true,
23
23
  "resolveJsonModule": true