Skip to content

Commit

Permalink
Revert "chore(build): Avoid prebuilding api side, instead use an esbu…
Browse files Browse the repository at this point in the history
…ild plugin" (#8056)

* Revert "chore(build): Avoid prebuilding api side, instead use an esbuild plugin (#7672)"

This reverts commit af7a998.

* add back chore changes
  • Loading branch information
jtoar authored Apr 19, 2023
1 parent 090ef33 commit e9d63f6
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 98 deletions.
4 changes: 2 additions & 2 deletions packages/api-server/src/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ const validate = async () => {
}
}

const rebuildApiServer = async () => {
const rebuildApiServer = () => {
try {
// Shutdown API server
killApiServer()

const buildTs = Date.now()
process.stdout.write(c.dim(c.italic('Building... ')))
await buildApi()
buildApi()
console.log(c.dim(c.italic('Took ' + (Date.now() - buildTs) + ' ms')))

const forkOpts = {
Expand Down
7 changes: 2 additions & 5 deletions packages/cli/src/commands/buildHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export const handler = async ({
},
side.includes('api') && {
title: 'Building API...',
task: async () => {
const { errors, warnings } = await buildApi()
task: () => {
const { errors, warnings } = buildApi()

if (errors.length) {
console.error(errors)
Expand Down Expand Up @@ -134,10 +134,7 @@ export const handler = async ({
'file://' + rwjsPaths.web.routes
)}.`
)

return
}

// Running a separate process here, otherwise it wouldn't pick up the
// generated Prisma Client due to require module caching
await execa('yarn rw prerender', {
Expand Down
47 changes: 23 additions & 24 deletions packages/cli/src/commands/deploy/__tests__/nftPack.test.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import fs from 'fs'
import path from 'path'

import { buildApi } from '@redwoodjs/internal/dist/build/api'
import { findApiDistFunctions } from '@redwoodjs/internal/dist/files'

import nftPacker from '../packing/nft'

jest.mock('@redwoodjs/internal/dist/files', () => {
return {
findApiDistFunctions: () => {
return [
'/Users/carmack/dev/redwood/__fixtures__/example-todo-main/api/dist/functions/graphql.js',
'/Users/carmack/dev/redwood/__fixtures__/example-todo-main/api/dist/functions/healthz/healthz.js',
'/Users/carmack/dev/redwood/__fixtures__/example-todo-main/api/dist/functions/invalid/x.js',
'/Users/carmack/dev/redwood/__fixtures__/example-todo-main/api/dist/functions/nested/nested.js',
'/Users/carmack/dev/redwood/__fixtures__/example-todo-main/api/dist/functions/x/index.js',
]
},
const FIXTURE_PATH = path.resolve(
__dirname,
'../../../../../../__fixtures__/example-todo-main'
)

let functionDistFiles

beforeAll(() => {
process.env.RWJS_CWD = FIXTURE_PATH

// Actually build the fixture, if we need it
if (!fs.existsSync(path.join(FIXTURE_PATH, 'api/dist/functions'))) {
buildApi()
}

functionDistFiles = findApiDistFunctions()
})

jest.mock('@redwoodjs/project-config', () => {
return {
getPaths: () => {
return {
base: '/Users/carmack/dev/redwood/__fixtures__/example-todo-main/',
}
},
ensurePosixPath: (path) => {
return path.replace(/\\/g, '/')
},
}
afterAll(() => {
delete process.env.RWJS_CWD
})

test('Check packager detects all functions', () => {
Expand All @@ -40,7 +39,7 @@ test('Check packager detects all functions', () => {
})

test('Creates entry file for nested functions correctly', () => {
const nestedFunction = findApiDistFunctions().find((fPath) =>
const nestedFunction = functionDistFiles.find((fPath) =>
fPath.includes('nested')
)

Expand All @@ -56,7 +55,7 @@ test('Creates entry file for nested functions correctly', () => {
})

test('Creates entry file for top level functions correctly', () => {
const graphqlFunction = findApiDistFunctions().find((fPath) =>
const graphqlFunction = functionDistFiles.find((fPath) =>
fPath.includes('graphql')
)

Expand Down
37 changes: 2 additions & 35 deletions packages/internal/src/__tests__/build_api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import compat from 'core-js-compat'

import { ensurePosixPath, getPaths } from '@redwoodjs/project-config'

import { cleanApiBuild } from '../build/api'
import { cleanApiBuild, prebuildApiFiles } from '../build/api'
import {
getApiSideBabelConfigPath,
getApiSideBabelPlugins,
getApiSideDefaultBabelConfig,
transformWithBabel,
BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS,
TARGETS_NODE,
} from '../build/babel/api'
Expand All @@ -22,31 +21,6 @@ const FIXTURE_PATH = path.resolve(
'../../../../__fixtures__/example-todo-main'
)

// @NOTE: we no longer prebuild files into the .redwood/prebuild folder
// However, prebuilding in the tests still helpful for us to validate
// that everything is working as expected.
export const prebuildApiFiles = (srcFiles: string[]) => {
const rwjsPaths = getPaths()
const plugins = getApiSideBabelPlugins()

return srcFiles.map((srcPath) => {
const relativePathFromSrc = path.relative(rwjsPaths.base, srcPath)
const dstPath = path
.join(rwjsPaths.generated.prebuild, relativePathFromSrc)
.replace(/\.(ts)$/, '.js')

const result = transformWithBabel(srcPath, plugins)
if (!result?.code) {
throw new Error(`Could not prebuild ${srcPath}`)
}

fs.mkdirSync(path.dirname(dstPath), { recursive: true })
fs.writeFileSync(dstPath, result.code)

return dstPath
})
}

const cleanPaths = (p) => {
return ensurePosixPath(path.relative(FIXTURE_PATH, p))
}
Expand Down Expand Up @@ -116,10 +90,6 @@ test.skip('api prebuild transforms gql with `babel-plugin-graphql-tag`', () => {
.filter((p) => p.endsWith('todos.sdl.js'))
.pop()

if (!p) {
throw new Error('No built files')
}

const code = fs.readFileSync(p, 'utf-8')
expect(code.includes('import gql from "graphql-tag";')).toEqual(false)
expect(code.includes('gql`')).toEqual(false)
Expand Down Expand Up @@ -523,7 +493,7 @@ test('jest mock statements also handle', () => {
cwd: getPaths().api.base,
// We override the plugins, to match packages/testing/config/jest/api/index.js
plugins: getApiSideBabelPlugins({ forJest: true }),
})?.code
}).code

// Step 2: check that output has correct import statement path
expect(outputForJest).toContain('import dog from "../../lib/dog"')
Expand All @@ -533,13 +503,10 @@ test('jest mock statements also handle', () => {

test('core-js polyfill list', () => {
const { list } = compat({
// @ts-expect-error TODO: Figure out why this is needed
targets: { node: TARGETS_NODE },
// @ts-expect-error TODO: Figure out why this is needed
version: BABEL_PLUGIN_TRANSFORM_RUNTIME_OPTIONS.corejs.version,
})

// TODO: Node.js 12? Really?
/**
* Redwood targets Node.js 12, but that doesn't factor into what gets polyfilled
* because Redwood uses the plugin-transform-runtime polyfill strategy.
Expand Down
77 changes: 46 additions & 31 deletions packages/internal/src/build/api.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,76 @@
import type { PluginBuild } from 'esbuild'
import { build } from 'esbuild'
import fs from 'fs'
import path from 'path'

import * as esbuild from 'esbuild'
import { removeSync } from 'fs-extra'

import { getPaths, getConfig } from '@redwoodjs/project-config'

import { findApiFiles } from '../files'

import { getApiSideBabelPlugins, transformWithBabel } from './babel/api'
import { getApiSideBabelPlugins, prebuildApiFile } from './babel/api'

export const buildApi = async () => {
export const buildApi = () => {
// TODO: Be smarter about caching and invalidating files,
// but right now we just delete everything.
cleanApiBuild()
return transpileApi(findApiFiles())

const srcFiles = findApiFiles()

const prebuiltFiles = prebuildApiFiles(srcFiles).filter(
(path): path is string => path !== undefined
)

return transpileApi(prebuiltFiles)
}

export const cleanApiBuild = () => {
const rwjsPaths = getPaths()
removeSync(rwjsPaths.api.dist)
removeSync(path.join(rwjsPaths.generated.prebuild, 'api'))
}

const runRwBabelTransformsPlugin = {
name: 'rw-esbuild-babel-transform',
setup(build: PluginBuild) {
build.onLoad({ filter: /\.(js|ts|tsx|jsx)$/ }, async (args) => {
// Remove RedwoodJS "magic" from a user's code leaving JavaScript behind.
const transformedCode = transformWithBabel(
args.path,
getApiSideBabelPlugins({
forJest: false,
openTelemetry: getConfig().experimental.opentelemetry.enabled,
})
)

if (transformedCode?.code) {
return {
contents: transformedCode.code,
loader: 'js',
}
}

throw new Error(`Could not transform file: ${args.path}`)
})
},
/**
* Remove RedwoodJS "magic" from a user's code leaving JavaScript behind.
*/
export const prebuildApiFiles = (srcFiles: string[]) => {
const rwjsPaths = getPaths()
const plugins = getApiSideBabelPlugins({
forJest: false,
openTelemetry: getConfig().experimental.opentelemetry.enabled,
})

return srcFiles.map((srcPath) => {
const relativePathFromSrc = path.relative(rwjsPaths.base, srcPath)
const dstPath = path
.join(rwjsPaths.generated.prebuild, relativePathFromSrc)
.replace(/\.(ts)$/, '.js')

const result = prebuildApiFile(srcPath, dstPath, plugins)
if (!result?.code) {
// TODO: Figure out a better way to return these programatically.
console.warn('Error:', srcPath, 'could not prebuilt.')

return undefined
}

fs.mkdirSync(path.dirname(dstPath), { recursive: true })
fs.writeFileSync(dstPath, result.code)

return dstPath
})
}

export const transpileApi = async (files: string[], options = {}) => {
export const transpileApi = (files: string[], options = {}) => {
const rwjsPaths = getPaths()

return build({
return esbuild.buildSync({
absWorkingDir: rwjsPaths.api.base,
entryPoints: files,
platform: 'node',
target: 'node16',
format: 'cjs',
bundle: false,
plugins: [runRwBabelTransformsPlugin],
outdir: rwjsPaths.api.dist,
// setting this to 'true' will generate an external sourcemap x.js.map
// AND set the sourceMappingURL comment
Expand Down
8 changes: 7 additions & 1 deletion packages/internal/src/build/babel/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,11 @@ export const registerApiSideBabelHook = ({
})
}

export const transformWithBabel = (
export const prebuildApiFile = (
srcPath: string,
// we need to know dstPath as well
// so we can generate an inline, relative sourcemap
dstPath: string,
plugins: TransformOptions['plugins']
) => {
const code = fs.readFileSync(srcPath, 'utf-8')
Expand All @@ -215,6 +218,9 @@ export const transformWithBabel = (
...defaultOptions,
cwd: getPaths().api.base,
filename: srcPath,
// we set the sourceFile (for the sourcemap) as a correct, relative path
// this is why this function (prebuildFile) must know about the dstPath
sourceFileName: path.relative(path.dirname(dstPath), srcPath),
// we need inline sourcemaps at this level
// because this file will eventually be fed to esbuild
// when esbuild finds an inline sourcemap, it tries to "combine" it
Expand Down

0 comments on commit e9d63f6

Please sign in to comment.