Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(build): treeshake build output #866

Merged
merged 7 commits into from
May 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ what follows is a stub
1. A new TypeScript instance is created so that the types generated in the previous step are picked up by the checker. This should be faster because it reuses the TypeScript cache created in the previous step.
1. The app is type checked
1. The app is transpiled
1. The app is emitted into `node_modules/.build`. This convention keeps derived files in a well known generally ignored location.
1. The app is emitted into `.nexus/build`. This convention keeps derived files in a well known generally ignored location.
1. A production-oriented start module is generated differing in the following ways:
- paths are relative
- typescript not hooked into module extensions
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/migrate-from-nexus-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ You should only be working with the `nexus` CLI. Below shows the example scripts
- "dev": "ts-node-dev --no-notify --respawn --transpileOnly src/server",
+ "dev": "nexus dev",
+ "build": "nexus build",
+ "start": "node node_modules/.build"
+ "start": "node .nexus/build"
},
```

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/project-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

- Out Root is the place where the transpiled TypeScript (to JavaScript) modules will be emitted to. The folder structure mimicks that of the source root.
- Out Root is defined by setting `compilerOptions.outDir`.
- If you do not specify it then Nexus will default to `node_modules/.build`. Unlike with `rootDir` Nexus will not scaffold the default into your `tsconfig.json` because its presence has no impact upon VSCode.
- If you do not specify it then Nexus will default to `.nexus/build`. Unlike with `rootDir` Nexus will not scaffold the default into your `tsconfig.json` because its presence has no impact upon VSCode.
- You can override its value interactively with `nexus build --out`.

##### Check-Only Builds
Expand Down
2 changes: 1 addition & 1 deletion docs/references/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ If you don't want to use a docker, here are some links to alternative approaches

```diff
+++ package.json
+ "start": "node node_modules/.build"
+ "start": "node .nexus/build"
```

3. In many cases this will be enough. Many deployment platforms will call into these scripts by default. You can customize where `build` outputs to if your deployment platform requires it. There are built in guides for `zeit` and `heroku` which will check your project is prepared for deployment to those respective platforms. Take advantage of them if applicable:
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@types/express": "^4.17.2",
"@types/node-fetch": "^2.5.5",
"@types/prompts": "^2.0.3",
"@zeit/node-file-trace": "^0.5.1",
"anymatch": "^3.1.1",
"arg": "^4.1.3",
"chalk": "^4.0.0",
Expand All @@ -41,7 +42,7 @@
"dotenv": "^8.2.0",
"express": "^4.17.1",
"fp-ts": "^2.5.4",
"fs-jetpack": "^2.2.3",
"fs-jetpack": "^2.4.0",
"get-port": "^5.1.0",
"graphql": "^14.5.8",
"http-errors": "^1.7.3",
Expand Down
3 changes: 3 additions & 0 deletions src/cli/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const BUILD_ARGS = {
'--stage': String,
'--entrypoint': String,
'-e': '--entrypoint',
'--no-bundle': Boolean,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure how to call the new output. It's not really a bundle, but then, what it is?

'--help': Boolean,
'-h': '--help',
}
Expand All @@ -36,6 +37,7 @@ export class Build implements Command {
output: args['--output'],
stage: args['--stage'],
entrypoint: args['--entrypoint'],
asBundle: args['--no-bundle'] !== true,
})
}

Expand All @@ -49,6 +51,7 @@ export class Build implements Command {
-o, --output Relative path to output directory
-e, --entrypoint Custom entrypoint to your app (default: app.ts)
-d, --deployment Enable custom build for some deployment platforms (${formattedSupportedDeployTargets})
--no-bundle Do not output build as a bundle
-h, --help Show this help message
`
}
Expand Down
16 changes: 9 additions & 7 deletions src/cli/commands/create/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { tsconfigTemplate } from '../../../lib/layout/tsconfig'
import { rootLogger } from '../../../lib/nexus-logger'
import { ownPackage } from '../../../lib/own-package'
import * as PackageManager from '../../../lib/package-manager'
import * as Plugin from '../../../lib/plugin'
import * as PluginRuntime from '../../../lib/plugin'
import * as PluginWorktime from '../../../lib/plugin/worktime'
import * as proc from '../../../lib/process'
import { createGitRepository, CWDProjectNameOrGenerate } from '../../../lib/utils'

Expand Down Expand Up @@ -52,8 +53,8 @@ export async function runLocalHandOff(): Promise<void> {

const parentData = await loadDataFromParentProcess()
const layout = await Layout.create()
const pluginM = await Plugin.getUsedPlugins(layout)
const plugins = Plugin.importAndLoadWorktimePlugins(pluginM, layout)
const pluginM = await PluginWorktime.getUsedPlugins(layout)
const plugins = PluginRuntime.importAndLoadWorktimePlugins(pluginM, layout)
log.trace('plugins', { plugins })

// TODO select a template
Expand Down Expand Up @@ -460,6 +461,7 @@ const templates: Record<TemplateName, TemplateCreator> = {
*/
async function scaffoldBaseFiles(options: InternalConfig) {
const appEntrypointPath = path.join(options.sourceRoot, 'app.ts')
const sourceRootRelative = path.relative(options.projectRoot, options.sourceRoot)

await Promise.all([
// Empty app and graphql module.
Expand Down Expand Up @@ -535,7 +537,7 @@ async function scaffoldBaseFiles(options: InternalConfig) {
format: "npx prettier --write './**/*.{ts,md}'",
dev: 'nexus dev',
build: 'nexus build',
start: 'node node_modules/.build',
start: `node .nexus/build/${sourceRootRelative}`,
},
prettier: {
semi: false,
Expand All @@ -550,7 +552,7 @@ async function scaffoldBaseFiles(options: InternalConfig) {
fs.writeAsync(
'tsconfig.json',
tsconfigTemplate({
sourceRootRelative: path.relative(options.projectRoot, options.sourceRoot),
sourceRootRelative,
outRootRelative: Layout.DEFAULT_BUILD_FOLDER_PATH_RELATIVE_TO_PROJECT_ROOT,
})
),
Expand Down Expand Up @@ -583,8 +585,8 @@ async function scaffoldBaseFiles(options: InternalConfig) {
const ENV_PARENT_DATA = 'NEXUS_CREATE_DATA'

type ParentData = {
database?: Plugin.OnAfterBaseSetupLens['database']
connectionURI?: Plugin.OnAfterBaseSetupLens['connectionURI']
database?: PluginRuntime.OnAfterBaseSetupLens['database']
connectionURI?: PluginRuntime.OnAfterBaseSetupLens['connectionURI']
}

async function loadDataFromParentProcess(): Promise<ParentData> {
Expand Down
32 changes: 25 additions & 7 deletions src/lib/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { stripIndent } from 'common-tags'
import * as FS from 'fs-jetpack'
import * as Path from 'path'
import * as Layout from '../../lib/layout'
import { compile, createTSProgram, deleteTSIncrementalFile } from '../../lib/tsc'
import { emitTSProgram, createTSProgram, deleteTSIncrementalFile } from '../../lib/tsc'
import {
createStartModuleContent,
prepareStartModule,
Expand All @@ -18,6 +18,7 @@ import {
normalizeTarget,
validateTarget,
} from './deploy-target'
import { bundle } from './bundle'

const log = rootLogger.child('build')

Expand All @@ -26,6 +27,8 @@ interface BuildSettings {
output?: string
stage?: string
entrypoint?: string
asBundle: boolean
cwd?: string
}

export async function buildNexusApp(settings: BuildSettings) {
Expand All @@ -36,8 +39,10 @@ export async function buildNexusApp(settings: BuildSettings) {
const buildOutput = settings.output ?? computeBuildOutputFromTarget(deploymentTarget) ?? undefined

const layout = await Layout.create({
buildOutput,
buildOutputDir: buildOutput,
asBundle: settings.asBundle,
entrypointPath: settings.entrypoint,
cwd: settings.cwd,
})

/**
Expand Down Expand Up @@ -85,7 +90,7 @@ export async function buildNexusApp(settings: BuildSettings) {
// incremental builder type of program so that the cache from the previous
// run of TypeScript should make re-building up this one cheap.

compile(tsBuilder, layout, { removePreviousBuild: false })
emitTSProgram(tsBuilder, layout, { removePreviousBuild: false })

const gotManifests = Plugin.getPluginManifests(plugins)

Expand All @@ -105,12 +110,25 @@ export async function buildNexusApp(settings: BuildSettings) {
})
),
})

if (layout.build.bundleOutputDir) {
log.info('bundling app')
await bundle({
base: layout.projectRoot,
bundleOutputDir: layout.build.bundleOutputDir,
entrypoint: layout.build.startModuleOutPath,
tsOutputDir: layout.build.tsOutputDir,
tsRootDir: layout.tsConfig.content.options.rootDir!,
plugins: pluginReflection.plugins,
})
await FS.removeAsync(layout.build.tsOutputDir)
}
}

const buildOutputLog =
layout.tsConfig.content.options.noEmit === true
? 'no emit'
: Path.relative(layout.projectRoot, layout.buildOutput)
: Path.relative(layout.projectRoot, layout.build.bundleOutputDir ?? layout.build.tsOutputDir)

log.info('success', {
buildOutput: buildOutputLog,
Expand Down Expand Up @@ -138,9 +156,9 @@ export async function writeStartModule({
// module. For example we can alias it, or, we can rename it e.g.
// `index_original.js`. For now we just error out and ask the user to not name
// their module index.ts.
if (FS.exists(layout.startModuleInPath)) {
if (FS.exists(layout.build.startModuleInPath)) {
fatal(stripIndent`
Found ${layout.startModuleInPath}
Found ${layout.build.startModuleInPath}
Nexus reserves the source root module name ${START_MODULE_NAME}.js for its own use.
Please change your app layout to not have this module.
This is a temporary limitation that we intend to remove in the future.
Expand All @@ -149,5 +167,5 @@ export async function writeStartModule({
}

log.trace('Writing start module to disk')
await FS.writeAsync(layout.startModuleOutPath, startModule)
await FS.writeAsync(layout.build.startModuleOutPath, startModule)
}
63 changes: 63 additions & 0 deletions src/lib/build/bundle.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { NodeFileTraceReasons } from '@zeit/node-file-trace'
import * as Path from 'path'
import { traceFiles } from './bundle'

const base = Path.dirname(require.resolve('../../../package.json'))
const entrypoint = Path.join(base, 'dist', 'index.js')

it('should not bundle typescript', async () => {
const { reasons } = await traceFiles({
base,
entrypoint,
plugins: [],
})
const isTypescriptBundled = isModuleBundled('node_modules/typescript/lib/typescript.js', reasons)

expect(isTypescriptBundled).toMatchInlineSnapshot(`false`)
})

it('should not bundle any of the cli', async () => {
const { files } = await traceFiles({
base,
entrypoint,
plugins: [],
})

const cliFiles = Array.from(files.keys()).filter((f) => f.includes('dist/cli'))

expect(cliFiles).toMatchInlineSnapshot(`Array []`)
})

function walkParents(parents: string[], reasons: NodeFileTraceReasons, path: Array<string | string[]>): void {
if (parents.length === 0) {
return
}

if (parents.length === 1) {
path.push(parents[0])
} else {
path.push(parents)
}

parents.forEach((p) => {
const module = reasons[p]
walkParents(module.parents, reasons, path)
})
}

export function isModuleBundled(
moduleId: string,
reasons: NodeFileTraceReasons
): false | { reason: Array<string | string[]> } {
const module = reasons[moduleId]

if (!module) {
return false
}

let path: (string | string[])[] = [moduleId]

walkParents(module.parents, reasons, path)

return { reason: path.reverse() }
}
Loading