From 8d4f7860fcf1588a1815a63bcca7000d95960b59 Mon Sep 17 00:00:00 2001 From: Gal Schlezinger Date: Sun, 28 May 2023 11:07:36 +0300 Subject: [PATCH] remove dynamism in imports (#351) --- .changeset/serious-moons-remain.md | 8 +++++ packages/primitives/scripts/build.ts | 29 +++++++++++++++++++ packages/primitives/src/injectSourceCode.d.ts | 6 ++++ packages/primitives/src/primitives/load.js | 29 ++++++++++++++----- 4 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 .changeset/serious-moons-remain.md create mode 100644 packages/primitives/src/injectSourceCode.d.ts diff --git a/.changeset/serious-moons-remain.md b/.changeset/serious-moons-remain.md new file mode 100644 index 00000000..0f121ff4 --- /dev/null +++ b/.changeset/serious-moons-remain.md @@ -0,0 +1,8 @@ +--- +'@edge-runtime/primitives': patch +--- + +remove dynamism in imports: add a `${primitive}.text.js` file that will be +required, instead of using `fs` to read the file at runtime. + +This will help bundlers to statically analyze the code. diff --git a/packages/primitives/scripts/build.ts b/packages/primitives/scripts/build.ts index a1492a3a..cd910995 100644 --- a/packages/primitives/scripts/build.ts +++ b/packages/primitives/scripts/build.ts @@ -142,6 +142,35 @@ async function bundlePackage() { ], }) + if (true) { + const loadSource = fs.promises.readFile( + resolve(__dirname, '../dist/load.js'), + 'utf8' + ) + const files = new Set() + const loadSourceWithPolyfills = (await loadSource).replace( + /injectSourceCode\("(.+)"\)/g, + (_, filename) => { + files.add(filename) + return `require(${JSON.stringify(`${filename}.text.js`)})` + } + ) + await fs.promises.writeFile( + resolve(__dirname, '../dist/load.js'), + loadSourceWithPolyfills + ) + for (const file of files) { + const contents = await fs.promises.readFile( + resolve(__dirname, '../dist', file), + 'utf8' + ) + await fs.promises.writeFile( + resolve(__dirname, '../dist', `${file}.text.js`), + `module.exports = ${JSON.stringify(contents)}` + ) + } + } + for (const file of filesExt.map((file) => parse(file).name)) { if (file !== 'index') { await fs.promises.mkdir(resolve(__dirname, `../${file}`)).catch(() => {}) diff --git a/packages/primitives/src/injectSourceCode.d.ts b/packages/primitives/src/injectSourceCode.d.ts new file mode 100644 index 00000000..a8b67233 --- /dev/null +++ b/packages/primitives/src/injectSourceCode.d.ts @@ -0,0 +1,6 @@ +/** + * Injects the source code of a file into a string. + * This relies on the build script to generate a file with the same name as the + * file to be injected, but with a `.text.js` extension. + */ +declare const injectSourceCode: (path: string) => string diff --git a/packages/primitives/src/primitives/load.js b/packages/primitives/src/primitives/load.js index 47e2d122..62a463d5 100644 --- a/packages/primitives/src/primitives/load.js +++ b/packages/primitives/src/primitives/load.js @@ -2,7 +2,6 @@ import Module from 'module' import { dirname, join } from 'path' -import { readFileSync } from 'fs' import nodeCrypto from 'crypto' /** @@ -12,22 +11,22 @@ import nodeCrypto from 'crypto' * @param {string} params.path * @param {Set} [params.references] * @param {Record} params.scopedContext + * @param {string} params.sourceCode * @returns {any} */ function requireWithFakeGlobalScope(params) { - const resolved = params.path const getModuleCode = `(function(module,exports,require,__dirname,__filename,globalThis,${Object.keys( params.scopedContext - ).join(',')}) {${readFileSync(resolved, 'utf-8')}\n})` + ).join(',')}) {${params.sourceCode}\n})` const module = { exports: {}, loaded: false, - id: resolved, + id: params.path, } // @ts-ignore const moduleRequire = (Module.createRequire || Module.createRequireFromPath)( - resolved + params.path ) /** @param {string} pathToRequire */ @@ -48,8 +47,8 @@ function requireWithFakeGlobalScope(params) { module, module.exports, throwingRequire, - dirname(resolved), - resolved, + dirname(params.path), + params.path, params.context, ...Object.values(params.scopedContext) ) @@ -68,6 +67,7 @@ export function load(scopedContext = {}) { const encodingImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './encoding.js'), + sourceCode: injectSourceCode('./encoding.js'), scopedContext: scopedContext, }) assign(context, { @@ -81,6 +81,7 @@ export function load(scopedContext = {}) { const consoleImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './console.js'), + sourceCode: injectSourceCode('./console.js'), scopedContext: scopedContext, }) assign(context, { console: consoleImpl.console }) @@ -89,6 +90,7 @@ export function load(scopedContext = {}) { const eventsImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './events.js'), + sourceCode: injectSourceCode('./events.js'), scopedContext: scopedContext, }) assign(context, { @@ -103,6 +105,7 @@ export function load(scopedContext = {}) { const streamsImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './streams.js'), + sourceCode: injectSourceCode('./streams.js'), scopedContext: { ...scopedContext }, }) @@ -110,6 +113,7 @@ export function load(scopedContext = {}) { const textEncodingStreamImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './text-encoding-streams.js'), + sourceCode: injectSourceCode('./text-encoding-streams.js'), scopedContext: { ...streamsImpl, ...scopedContext }, }) @@ -128,6 +132,7 @@ export function load(scopedContext = {}) { const abortControllerImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './abort-controller.js'), + sourceCode: injectSourceCode('./abort-controller.js'), scopedContext: { ...eventsImpl, ...scopedContext }, }) assign(context, { @@ -140,6 +145,7 @@ export function load(scopedContext = {}) { const urlImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './url.js'), + sourceCode: injectSourceCode('./url.js'), scopedContext: { ...scopedContext }, }) assign(context, { @@ -152,6 +158,7 @@ export function load(scopedContext = {}) { const blobImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './blob.js'), + sourceCode: injectSourceCode('./blob.js'), scopedContext: { ...streamsImpl, ...scopedContext }, }) assign(context, { @@ -162,6 +169,7 @@ export function load(scopedContext = {}) { const structuredCloneImpl = requireWithFakeGlobalScope({ path: join(__dirname, './structured-clone.js'), context, + sourceCode: injectSourceCode('./structured-clone.js'), scopedContext: { ...streamsImpl, ...scopedContext }, }) assign(context, { @@ -172,6 +180,7 @@ export function load(scopedContext = {}) { const fetchImpl = requireWithFakeGlobalScope({ context, path: join(__dirname, './fetch.js'), + sourceCode: injectSourceCode('./fetch.js'), cache: new Map([ ['abort-controller', { exports: abortControllerImpl }], ['streams', { exports: streamsImpl }], @@ -218,9 +227,12 @@ function getCrypto(context, scopedContext) { CryptoKey: scopedContext.CryptoKey || globalThis.CryptoKey, SubtleCrypto: scopedContext.SubtleCrypto || globalThis.SubtleCrypto, } - } else if (nodeCrypto.webcrypto) { + } else if ( // @ts-ignore + nodeCrypto.webcrypto + ) { /** @type {any} */ + // @ts-ignore const webcrypto = nodeCrypto.webcrypto return { crypto: webcrypto, @@ -233,6 +245,7 @@ function getCrypto(context, scopedContext) { return requireWithFakeGlobalScope({ context, path: join(__dirname, './crypto.js'), + sourceCode: injectSourceCode('./crypto.js'), scopedContext: { ...scopedContext, },