Skip to content

Commit ef70de6

Browse files
authored
chore: vendor deno modules used in integration test helpers (#2883)
1 parent 17a1202 commit ef70de6

File tree

5 files changed

+98
-37
lines changed

5 files changed

+98
-37
lines changed

.github/workflows/run-tests.yml

+2
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ jobs:
151151
run: npm ci
152152
- name: 'Build'
153153
run: npm run build
154+
- name: 'Vendor deno helpers for integration tests'
155+
run: node tools/vendor-deno-tools.js
154156
- name: Resolve Next.js version
155157
id: resolve-next-version
156158
shell: bash

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ node_modules/
22
dist/
33
.next
44
edge-runtime/vendor
5+
# deno.json is ephemeral and generated for the purpose of vendoring remote modules in CI
6+
tools/deno/deno.json
7+
tools/deno/vendor
58

69
# Local Netlify folder
710
.netlify

tools/build-helpers.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { createWriteStream } from 'node:fs'
2+
import { rm, writeFile } from 'node:fs/promises'
3+
import { join } from 'node:path'
4+
import { Readable } from 'stream'
5+
import { finished } from 'stream/promises'
6+
7+
import { execaCommand } from 'execa'
8+
9+
/**
10+
* @param {Object} options
11+
* @param {string} options.vendorSource Path to the file to vendor
12+
* @param {string} options.cwd Directory to run the command in
13+
* @param {string[]} [options.wasmFilesToDownload] List of wasm files to download
14+
* @param {boolean} [options.initEmptyDenoJson] If true, will create an empty deno.json file
15+
*/
16+
export async function vendorDeno({
17+
vendorSource,
18+
cwd,
19+
wasmFilesToDownload = [],
20+
initEmptyDenoJson = false,
21+
}) {
22+
try {
23+
await execaCommand('deno --version')
24+
} catch {
25+
throw new Error('Could not check the version of Deno. Is it installed on your system?')
26+
}
27+
28+
const vendorDest = join(cwd, 'vendor')
29+
30+
console.log(`🧹 Deleting '${vendorDest}'...`)
31+
32+
await rm(vendorDest, { force: true, recursive: true })
33+
34+
if (initEmptyDenoJson) {
35+
const denoJsonPath = join(cwd, 'deno.json')
36+
console.log(`🧹 Generating clean '${denoJsonPath}`)
37+
await writeFile(denoJsonPath, '{}')
38+
}
39+
40+
console.log(`📦 Vendoring Deno modules for '${vendorSource}' into '${vendorDest}'...`)
41+
// --output=${vendorDest}
42+
await execaCommand(`deno vendor ${vendorSource} --force`, {
43+
cwd,
44+
})
45+
46+
if (wasmFilesToDownload.length !== 0) {
47+
console.log(`⬇️ Downloading wasm files...`)
48+
49+
// deno vendor doesn't work well with wasm files
50+
// see https://github.com/denoland/deno/issues/14123
51+
// to workaround this we copy the wasm files manually
52+
// (note Deno 2 allows to vendor wasm files, but it also require modules to import them and not fetch and instantiate them
53+
// se being able to drop downloading is dependent on implementation of wasm handling in external modules as well)
54+
await Promise.all(
55+
wasmFilesToDownload.map(async (urlString) => {
56+
const url = new URL(urlString)
57+
58+
const destination = join(vendorDest, url.hostname, url.pathname)
59+
60+
const res = await fetch(url)
61+
if (!res.ok)
62+
throw new Error(`Failed to fetch .wasm file to vendor. Response status: ${res.status}`)
63+
const fileStream = createWriteStream(destination, { flags: 'wx' })
64+
await finished(Readable.fromWeb(res.body).pipe(fileStream))
65+
}),
66+
)
67+
}
68+
69+
console.log(`✅ Vendored Deno modules for '${vendorSource}' into '${vendorDest}'`)
70+
}

tools/build.js

+10-37
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
import { createWriteStream } from 'node:fs'
21
import { cp, readFile, rm } from 'node:fs/promises'
32
import { dirname, join, resolve } from 'node:path'
43
import { fileURLToPath } from 'node:url'
5-
import { Readable } from 'stream'
6-
import { finished } from 'stream/promises'
74

85
import { build, context } from 'esbuild'
9-
import { execaCommand } from 'execa'
106
import glob from 'fast-glob'
117

8+
import { vendorDeno } from './build-helpers.js'
9+
1210
const OUT_DIR = 'dist'
1311
await rm(OUT_DIR, { force: true, recursive: true })
1412

@@ -83,47 +81,22 @@ async function bundle(entryPoints, format, watch) {
8381
})
8482
}
8583

86-
async function vendorDeno() {
84+
async function vendorMiddlewareDenoModules() {
8785
const vendorSource = resolve('edge-runtime/vendor.ts')
88-
const vendorDest = resolve('edge-runtime/vendor')
89-
90-
try {
91-
await execaCommand('deno --version')
92-
} catch {
93-
throw new Error('Could not check the version of Deno. Is it installed on your system?')
94-
}
95-
96-
console.log(`🧹 Deleting '${vendorDest}'...`)
97-
98-
await rm(vendorDest, { force: true, recursive: true })
86+
const middlewareDir = resolve('edge-runtime')
9987

100-
console.log(`📦 Vendoring Deno modules into '${vendorDest}'...`)
101-
102-
await execaCommand(`deno vendor ${vendorSource} --output=${vendorDest} --force`)
103-
104-
// htmlrewriter contains wasm files and those don't currently work great with vendoring
105-
// see https://github.com/denoland/deno/issues/14123
106-
// to workaround this we copy the wasm files manually
107-
const filesToDownload = ['https://deno.land/x/[email protected]/pkg/htmlrewriter_bg.wasm']
108-
await Promise.all(
109-
filesToDownload.map(async (urlString) => {
110-
const url = new URL(urlString)
111-
112-
const destination = join(vendorDest, url.hostname, url.pathname)
113-
114-
const res = await fetch(url)
115-
if (!res.ok) throw new Error('Failed to fetch .wasm file to vendor', { cause: err })
116-
const fileStream = createWriteStream(destination, { flags: 'wx' })
117-
await finished(Readable.fromWeb(res.body).pipe(fileStream))
118-
}),
119-
)
88+
await vendorDeno({
89+
vendorSource,
90+
cwd: middlewareDir,
91+
wasmFilesToDownload: ['https://deno.land/x/[email protected]/pkg/htmlrewriter_bg.wasm'],
92+
})
12093
}
12194

12295
const args = new Set(process.argv.slice(2))
12396
const watch = args.has('--watch') || args.has('-w')
12497

12598
await Promise.all([
126-
vendorDeno(),
99+
vendorMiddlewareDenoModules(),
127100
bundle(entryPointsESM, 'esm', watch),
128101
...entryPointsCJS.map((entry) => bundle([entry], 'cjs', watch)),
129102
cp('src/build/templates', join(OUT_DIR, 'build/templates'), { recursive: true, force: true }),

tools/vendor-deno-tools.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { dirname, join } from 'node:path'
2+
import { fileURLToPath } from 'node:url'
3+
4+
import { vendorDeno } from './build-helpers.js'
5+
6+
const denoToolsDirectory = join(dirname(fileURLToPath(import.meta.url)), 'deno')
7+
8+
await vendorDeno({
9+
vendorSource: join(denoToolsDirectory, 'eszip.ts'),
10+
cwd: denoToolsDirectory,
11+
wasmFilesToDownload: ['https://deno.land/x/[email protected]/eszip_wasm_bg.wasm'],
12+
initEmptyDenoJson: true,
13+
})

0 commit comments

Comments
 (0)