Skip to content

Commit

Permalink
feat: add static readFileSync support in bundles
Browse files Browse the repository at this point in the history
  • Loading branch information
ChALkeR committed Nov 9, 2024
1 parent 98815b9 commit 2fd1526
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 9 deletions.
43 changes: 36 additions & 7 deletions bundler/bundle.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { existsSync } from 'node:fs'
import { existsSync, readFileSync } from 'node:fs'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { basename, dirname, extname, resolve, join } from 'node:path'
import { createRequire } from 'node:module'
Expand Down Expand Up @@ -165,6 +165,26 @@ export const build = async (...files) => {
}

const fsfiles = await getPackageFiles(filename ? dirname(resolve(filename)) : process.cwd())
const fsFilesContents = new Map()
loadPipeline.push(async (source) => {
const cwd = process.cwd()
for (const re of [/readFileSync\('([^'\\]+)'[),]/gu, /readFileSync\("([^"\\]+)"[),]/gu]) {
for (const match of source.matchAll(re)) {
const file = match[1]
if (file && /^[a-z0-9@_./-]+$/iu.test(file)) {
if (!resolve(file).startsWith(`${cwd}/`)) continue
const data = readFileSync(file, 'base64')
if (fsFilesContents.has(file)) {
assert(fsFilesContents.get(file) === data)
} else {
fsFilesContents.set(file, data)
}
}
}
}

return source
})

const hasBuffer = ['node', 'bun'].includes(options.platform)
const api = (f) => resolveRequire(`./modules/${f}`)
Expand All @@ -191,10 +211,7 @@ export const build = async (...files) => {
zlib: resolveRequire('browserify-zlib'),
}

const defines = {}
if (files.length === 1) defines['process.argv'] = stringify(['exodus-test', resolve(files[0])])

const res = await buildWrap({
const config = {
logLevel: 'silent',
stdin: {
contents: `(async function () {\n${main}\n})()`,
Expand All @@ -206,7 +223,6 @@ export const build = async (...files) => {
platform: 'neutral',
mainFields: ['browser', 'module', 'main'],
define: {
...defines,
'process.env.FORCE_COLOR': stringify('0'),
'process.env.NO_COLOR': stringify('1'),
'process.env.NODE_ENV': stringify(process.env.NODE_ENV),
Expand All @@ -233,6 +249,7 @@ export const build = async (...files) => {
EXODUS_TEST_SNAPSHOTS: stringify(EXODUS_TEST_SNAPSHOTS),
EXODUS_TEST_RECORDINGS: stringify(EXODUS_TEST_RECORDINGS),
EXODUS_TEST_FSFILES: stringify(fsfiles), // TODO: can we safely use relative paths?
EXODUS_TEST_FSFILES_CONTENTS: stringify([...fsFilesContents.entries()]),
},
alias: {
// Jest, tape and node:test
Expand Down Expand Up @@ -286,9 +303,21 @@ export const build = async (...files) => {
},
},
],
})
}

if (files.length === 1)
config.define['process.argv'] = stringify(['exodus-test', resolve(files[0])])

let res = await buildWrap(config)
assert.equal(res instanceof Error, res.errors.length > 0)

if (fsFilesContents.size > 0) {
// re-run as we detected that tests depend on fsReadFileSync contents
config.define.EXODUS_TEST_FSFILES_CONTENTS = stringify([...fsFilesContents.entries()])
res = await buildWrap(config)
assert.equal(res instanceof Error, res.errors.length > 0)
}

// if (res.errors.length === 0) require('fs').copyFileSync(outfile, 'tempout.cjs') // DEBUG

// We treat warnings as errors, so just merge all them
Expand Down
26 changes: 24 additions & 2 deletions bundler/modules/fs.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,33 @@ const promises = { ...stubsPromises, constants }
// eslint-disable-next-line no-undef
const fsFiles = typeof EXODUS_TEST_FSFILES === 'undefined' ? null : new Set(EXODUS_TEST_FSFILES)
const existsSync = (file) => {
if (fsFiles.has(file)) return true
if (fsFiles?.has(file)) return true
err('existsSync', file)
}

const readFileSync = (file /*, options */) => {
const fsFilesContents =
// eslint-disable-next-line no-undef
typeof EXODUS_TEST_FSFILES_CONTENTS === 'undefined' ? null : new Map(EXODUS_TEST_FSFILES_CONTENTS)
const readFileSync = (file, options) => {
let encoding
if (typeof options === 'string') {
encoding = options
} else if (options !== undefined) {
if (typeof options !== 'object') throw new Error('Unexpected readFileSync options')
const { encoding: enc, ...rest } = options
if (enc !== undefined && typeof enc !== 'string') throw new Error('encoding should be a string')
encoding = enc
if (Object.keys(rest).length > 0) throw new Error('Unsupported readFileSync options')
}

if (typeof file !== 'string') throw new Error('file argument should be string')
if (fsFilesContents?.has(file)) {
const data = Buffer.from(fsFilesContents.get(file), 'base64')
if (encoding?.toLowerCase().replace('-', '') === 'utf8') return data.toString('utf8')
if (encoding === undefined) return data
throw new Error('Unsupported encoding')
}

err('readFileSync', file)
}

Expand Down

0 comments on commit 2fd1526

Please sign in to comment.