Skip to content
This repository has been archived by the owner on May 22, 2024. It is now read-only.

Commit

Permalink
feat: include side files with NFT (#1614)
Browse files Browse the repository at this point in the history
* feat: return `jsModuleFormat` property

* feat: add bundled paths to `inputs`

* feat: force bundler to NFT in V2 functions

* chore: fix test

* refactor: rename property

* feat: add CJS shim

* feat: include side files when NFT is the default bundler

* chore: remove whitespace

* chore: update test
  • Loading branch information
eduardoboucas committed Oct 13, 2023
1 parent 9f3dab8 commit bf7a4ac
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 5 deletions.
22 changes: 21 additions & 1 deletion src/runtimes/node/bundlers/nft/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getNodeSupportMatrix } from '../../utils/node_version.js'
import type { GetSrcFilesFunction, BundleFunction } from '../types.js'

import { processESM } from './es_modules.js'
import { getSideFiles } from './side_files.js'
import { transform, getTransformer } from './transformer.js'

const appearsToBeModuleName = (name: string) => !name.startsWith('.')
Expand All @@ -29,8 +30,27 @@ const bundle: BundleFunction = async ({
pluginsModulesPath,
repositoryRoot = basePath,
runtimeAPIVersion,
srcPath,
stat,
}) => {
const { includedFiles = [], includedFilesBasePath } = config
const { includedFiles = [], includedFilesBasePath, nodeBundler } = config

// In the legacy `zisi` bundler, functions defined in a sub-directory had any
// side files automatically added to the bundle. We replicate this behavior
// here when all the following conditions are met:
//
// 1. The `traceWithNft` flag is enabled, meaning we're in the process of
// replacing the legacy bundler with NFT
// 2. No bundler configuration was supplied, meaning the default option is
// being used
// 3. The function uses the V1 syntax, since we don't want to extend this
// behavior to V2 functions
if (featureFlags.traceWithNft && nodeBundler === undefined && runtimeAPIVersion === 1) {
const sideFiles = await getSideFiles(srcPath, stat)

includedFiles.push(...sideFiles)
}

const { excludePatterns, paths: includedFilePaths } = await getPathsOfIncludedFiles(
includedFiles,
includedFilesBasePath || basePath,
Expand Down
25 changes: 25 additions & 0 deletions src/runtimes/node/bundlers/nft/side_files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Stats } from 'fs'
import { basename } from 'path'

import { isJunk } from 'junk'

import { glob } from '../../../../utils/matching.js'

/**
* Takes a function path and, if it's a directory, returns a list of all the
* nested files, recursively, except `node_modules` and junk files.
*/
export const getSideFiles = async function (functionPath: string, stat: Stats): Promise<string[]> {
if (!stat.isDirectory()) {
return []
}

const paths = await glob(`${functionPath}/**`, {
absolute: true,
cwd: functionPath,
ignore: `**/node_modules/**`,
nodir: true,
})

return paths.filter((path) => !isJunk(basename(path)))
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
exports.handler = () => ({
body: "Hello"
})

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tests/fixtures/directory-side-files/function-v1/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Robots are nice 🤖
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default async () => new Response("Hello")

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tests/fixtures/directory-side-files/function-v2/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Robots are nice 🤖
Empty file.
46 changes: 42 additions & 4 deletions tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,44 @@ describe('zip-it-and-ship-it', () => {
expect(files[0].mainFile).toBe(join(FIXTURES_DIR, fixtureName, 'function', 'function.js'))
})

testMany(
'Includes side files when the function is in a subdirectory',
[...allBundleConfigs],
async (options, variant) => {
const shouldIncludeFiles = variant === 'bundler_default' || variant === 'bundler_default_nft'
const { files } = await zipFixture('directory-side-files', { length: 2, opts: options })
const unzippedFunctions = await unzipFiles(files)
const [funcV1, funcV2] = unzippedFunctions
const expectedBundler = {
bundler_default: 'zisi',
bundler_esbuild: 'esbuild',
bundler_esbuild_zisi: 'esbuild',
bundler_default_nft: 'nft',
bundler_nft: 'nft',
}

expect(funcV1.bundler).toBe(expectedBundler[variant])
expect(funcV1.runtimeAPIVersion).toBe(1)

if (shouldIncludeFiles) {
await expect(`${funcV1.unzipPath}/robots.txt`).toPathExist()
await expect(`${funcV1.unzipPath}/sub-dir/sub-file.js`).toPathExist()
} else {
await expect(`${funcV1.unzipPath}/robots.txt`).not.toPathExist()
await expect(`${funcV1.unzipPath}/sub-dir/sub-file.js`).not.toPathExist()
}

await expect(`${funcV1.unzipPath}/Desktop.ini`).not.toPathExist()
await expect(`${funcV1.unzipPath}/node_modules`).not.toPathExist()

expect(funcV2.bundler).toBe('nft')
expect(funcV2.runtimeAPIVersion).toBe(2)
await expect(`${funcV2.unzipPath}/robots.txt`).not.toPathExist()
await expect(`${funcV2.unzipPath}/Desktop.ini`).not.toPathExist()
await expect(`${funcV2.unzipPath}/node_modules`).not.toPathExist()
},
)

testMany('Can target a directory with an index.js file', [...allBundleConfigs], async (options) => {
const fixtureName = 'index-handler'
const { files } = await zipFixture(fixtureName, {
Expand Down Expand Up @@ -2386,11 +2424,11 @@ describe('zip-it-and-ship-it', () => {

expect(func1Entry.displayName).toBe('Internal Function')
expect(func1Entry.generator).toBe('@netlify/mock-plugin@1.0.0')
expect(func1Entry.config.includedFiles).toEqual(['blog/*.md'])
expect(func2Entry.config.includedFiles).toEqual(['blog/*.md'])
expect(func1Entry.config.includedFiles?.includes('blog/*.md')).toBeTruthy()
expect(func2Entry.config.includedFiles?.includes('blog/*.md')).toBeTruthy()
expect(func2Entry.generator).toBeUndefined()
expect(func3Entry.config.includedFiles).toBe(undefined)
expect(func4Entry.config.includedFiles).toBe(undefined)
expect(func3Entry.config.includedFiles?.includes('blog/*.md')).toBeFalsy()
expect(func4Entry.config.includedFiles?.includes('blog/*.md')).toBeFalsy()

const functionPaths = [
join(func1Entry.unzipPath, 'internal-function.js'),
Expand Down

1 comment on commit bf7a4ac

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⏱ Benchmark results

  • largeDepsEsbuild: 2.7s
  • largeDepsNft: 8.1s
  • largeDepsZisi: 16s

Please sign in to comment.