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(deno): support esbuild #310

Merged
merged 12 commits into from
Jul 19, 2023
386 changes: 367 additions & 19 deletions e2e/deno-e2e/tests/deno-integrated.spec.ts

Large diffs are not rendered by default.

53 changes: 41 additions & 12 deletions e2e/deno-e2e/tests/deno-standalone.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ describe('Deno standalone app', () => {
writeFileSync(join(tmpProjPath(), 'assets/ignore.hbs'), 'IGNORE ME');
writeFileSync(join(tmpProjPath(), 'assets/git-ignore.hbs'), 'IGNORE ME');
writeFileSync(join(tmpProjPath(), 'assets/nx-ignore.hbs'), 'IGNORE ME');
writeFileSync(join(tmpProjPath(), 'assets/a/b/nested-ignore.hbs'), 'IGNORE ME');
writeFileSync(
join(tmpProjPath(), 'assets/a/b/nested-ignore.hbs'),
'IGNORE ME'
);

const project = readJson(`project.json`);
project.targets.build.options.assets = [
Expand All @@ -100,17 +103,27 @@ describe('Deno standalone app', () => {
expect(result.stdout).toContain(
`Successfully ran target build for project ${appName}`
);
expect(() => checkFilesExist(
`dist/${appName}/main.js`,
`dist/${appName}/LICENSE`,
`dist/${appName}/README.md`,
`dist/${appName}/assets/test1.hbs`,
`dist/${appName}/assets/test2.hbs`
)).not.toThrow();
expect(workspaceFileExists(`dist/${appName}/assets/ignore.hbs`)).toBeFalsy();
expect(workspaceFileExists(`dist/${appName}/assets/git-ignore.hbs`)).toBeFalsy();
expect(workspaceFileExists(`dist/${appName}/assets/nx-ignore.hbs`)).toBeFalsy();
expect(workspaceFileExists(`dist/${appName}/assets/a/b/nested-ignore.hbs`)).toBeFalsy();
expect(() =>
checkFilesExist(
`dist/${appName}/main.js`,
`dist/${appName}/LICENSE`,
`dist/${appName}/README.md`,
`dist/${appName}/assets/test1.hbs`,
`dist/${appName}/assets/test2.hbs`
)
).not.toThrow();
expect(
workspaceFileExists(`dist/${appName}/assets/ignore.hbs`)
).toBeFalsy();
expect(
workspaceFileExists(`dist/${appName}/assets/git-ignore.hbs`)
).toBeFalsy();
expect(
workspaceFileExists(`dist/${appName}/assets/nx-ignore.hbs`)
).toBeFalsy();
expect(
workspaceFileExists(`dist/${appName}/assets/a/b/nested-ignore.hbs`)
).toBeFalsy();
}, 120_000);

it('should serve deno app', async () => {
Expand Down Expand Up @@ -386,5 +399,21 @@ console.log(${fnName}())`
});
await promisifiedTreeKill(p.pid, 'SIGKILL');
}, 120_000);

it('should be able to use import alias of lib in app for build', async () => {
const fnName = names(libName).propertyName;
updateFile(
`src/main.ts`,
`import { ${fnName} } from '@proj/${libName}'

console.log(${fnName}())`
);

const result = await runNxCommandAsync(`build ${appName}`);
expect(result.stdout).toContain(
`Successfully ran target build for project ${appName}`
);
expect(workspaceFileExists(`dist/${appName}/main.js`)).toBeTruthy();
}, 120_000);
});
});
5 changes: 5 additions & 0 deletions packages/deno/executors.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
"schema": "./src/executors/emit/schema.json",
"description": "Bundle or transpile to a JavaScript file."
},
"esbuild": {
"implementation": "./src/executors/esbuild/esbuild.impl",
"schema": "./src/executors/esbuild/schema.json",
"description": "Bundle a package using EsBuild."
},
"run": {
"implementation": "./src/executors/run/run.impl",
"schema": "./src/executors/run/schema.json",
Expand Down
2 changes: 1 addition & 1 deletion packages/deno/src/executors/emit/emit.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import * as chalk from 'chalk';
import { dirname, join, resolve } from 'path';
import { BuildExecutorSchema } from './schema';

import { CopyAssetsHandler } from '@nx/js/src/utils/assets/copy-assets-handler';
import { ensureDirSync, unlinkSync, writeFileSync } from 'fs-extra';
import { CopyAssetsHandler } from '@nx/js/src/utils/assets/copy-assets-handler'
import { processCommonArgs } from '../../utils/arg-utils';
import { assertDenoInstalled, runDeno } from '../../utils/run-deno';

Expand Down
150 changes: 150 additions & 0 deletions packages/deno/src/executors/esbuild/esbuild.impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { ExecutorContext, joinPathFragments, stripIndents } from '@nx/devkit';
import { dirname, join, resolve } from 'path';
import { BuildExecutorSchema } from './schema';

import { CopyAssetsHandler } from '@nx/js/src/utils/assets/copy-assets-handler';
import { ensureDirSync, unlinkSync, writeFileSync } from 'fs-extra';
import { processCommonArgs } from '../../utils/arg-utils';
import { assertDenoInstalled, runDeno } from '../../utils/run-deno';

export async function denoEsbuildExecutor(
options: BuildExecutorSchema,
context: ExecutorContext
) {
assertDenoInstalled();
const opts = normalizeOptions(options, context);
const args = createArgs(opts, context);

const projectRoot = context.projectGraph.nodes[context.projectName].data.root;
const outputPath = dirname(opts.outputFile);

const assetHandler = new CopyAssetsHandler({
projectDir: projectRoot,
rootDir: context.root,
outputDir: outputPath,
assets: opts.assets,
});

const esbuildResult = await new Promise<boolean>((resolve) => {
const runningDenoProcess = runDeno(args);

runningDenoProcess.on('exit', (code) => {
resolve(code === 0);
});
});

if (esbuildResult !== true) {
return { success: false };
}

let copyAssetsResult = true;
try {
await assetHandler.processAllAssetsOnce();
} catch (e) {
if (process.env.NX_VERBOSE_LOGGING === 'true') {
console.error(e);
}
copyAssetsResult = false;
}

return { success: copyAssetsResult };
}

function normalizeOptions(
options: BuildExecutorSchema,
context: ExecutorContext
) {
// TODO: we might need to normalize paths here to make sure it works on windows?
if (!options.denoConfig) {
throw new Error('denoConfig is required');
}
if (!options.main) {
throw new Error('main is required');
}
if (!options.outputFile) {
options.outputFile = join('dist', options.main);
}

ensureDirSync(resolve(context.root, dirname(options.outputFile)));

options.bundle ??= true;

options.sourceMap ??= 'inline';
if (options.sourceMap === true) {
options.sourceMap = 'inline';
}

return options;
}

function createArgs(options: BuildExecutorSchema, context: ExecutorContext) {
const tmpBundleFile = createTempEsbuildFile(options, context);
const args = ['run', '--allow-all'];

args.push(...processCommonArgs(options));

args.push(tmpBundleFile);

return args;
}

function createTempEsbuildFile(
options: BuildExecutorSchema,
context: ExecutorContext
) {
const project = context.projectGraph.nodes[context.projectName];
const tmpBundleFile = join(
context.root,
'tmp',
project.data.root,
'deno-esbuild.ts'
);
// on windows paths get mistranslated to single slash, C:\blah, which causes issues in deno.
// use unix style path with file:/// protocol instead to avoid this.
const configFilePath = joinPathFragments(context.root, options.denoConfig);
const mainFilePath = joinPathFragments(context.root, options.main);
const outputFilePath = joinPathFragments(context.root, options.outputFile);

const content = stripIndents`
import * as esbuild from "https://deno.land/x/[email protected]/mod.js";
import { denoPlugins } from "https://deno.land/x/[email protected]/mod.ts";

const result = await esbuild.build({
plugins: [
...denoPlugins({
loader: "native",
configPath: "${configFilePath}"
})
],
entryPoints: ["${mainFilePath}"],
outfile: "${outputFilePath}",
bundle: ${options.bundle},
sourcemap: ${
options.sourceMap === false ? false : `"${options.sourceMap}"`
},
format: "esm",
});

console.log(result.outputFiles);

esbuild.stop();
`;

process.on('exit', () => cleanupTmpBundleFile(tmpBundleFile));
ensureDirSync(dirname(tmpBundleFile));
writeFileSync(tmpBundleFile, content);

return tmpBundleFile;
}

function cleanupTmpBundleFile(tmpFile: string) {
try {
if (tmpFile) {
unlinkSync(tmpFile);
}
} catch (e) {
// nothing
}
}

export default denoEsbuildExecutor;
10 changes: 10 additions & 0 deletions packages/deno/src/executors/esbuild/schema.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { AssetGlob } from '@nx/js/src/assets/assets';

export interface BuildExecutorSchema {
denoConfig: string;
main: string;
outputFile: string;
bundle?: boolean;
assets?: Array<AssetGlob | string>;
sourceMap?: boolean | 'linked' | 'inline';
}
78 changes: 78 additions & 0 deletions packages/deno/src/executors/esbuild/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"$schema": "http://json-schema.org/schema",
"version": 2,
"title": "Deno Emit Executor",
"description": "Output a single JavaScript file with all dependencies",
"type": "object",
"properties": {
"denoConfig": {
"type": "string",
"description": "Path to the Deno configuration file."
},
"main": {
"type": "string",
"description": "The entry point for your Deno application."
},
"outputFile": {
"type": "string",
"description": "The destination of the emitted JavaScript file."
},
"bundle": {
"type": "boolean",
"description": "Bundle all imports into a single JavaScript file.",
"default": true
},
"assets": {
"type": "array",
"description": "List of static assets.",
"default": [],
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"sourceMap": {
"oneOf": [
{ "type": "string", "enum": ["linked", "inline"] },
{ "type": "boolean" }
],
"description": "Generate sourceMap.",
"default": "inline"
}
},
"required": ["main", "outputFile", "denoConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
{
"type": "object",
"properties": {
"glob": {
"type": "string",
"description": "The pattern to match."
},
"input": {
"type": "string",
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
},
"ignore": {
"description": "An array of globs to ignore.",
"type": "array",
"items": {
"type": "string"
}
},
"output": {
"type": "string",
"description": "Absolute path within the output."
}
},
"additionalProperties": false,
"required": ["glob", "input", "output"]
},
{
"type": "string"
}
]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Object {
"tags": Array [],
"targets": Object {
"build": Object {
"executor": "@nx/deno:emit",
"executor": "@nx/deno:esbuild",
"options": Object {
"denoConfig": "apps/my-app/deno.json",
"main": "apps/my-app/src/main.ts",
Expand Down Expand Up @@ -56,7 +56,7 @@ Object {
"tags": Array [],
"targets": Object {
"build": Object {
"executor": "@nx/deno:emit",
"executor": "@nx/deno:esbuild",
"options": Object {
"denoConfig": "apps/my-oak-api/deno.json",
"main": "apps/my-oak-api/src/main.ts",
Expand Down Expand Up @@ -137,7 +137,7 @@ Object {
"tags": Array [],
"targets": Object {
"build": Object {
"executor": "@nx/deno:emit",
"executor": "@nx/deno:esbuild",
"options": Object {
"denoConfig": "deno.json",
"main": "src/main.ts",
Expand Down Expand Up @@ -183,7 +183,7 @@ Object {
"tags": Array [],
"targets": Object {
"build": Object {
"executor": "@nx/deno:emit",
"executor": "@nx/deno:esbuild",
"options": Object {
"denoConfig": "deno.json",
"main": "src/main.ts",
Expand Down
3 changes: 2 additions & 1 deletion packages/deno/src/generators/application/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ export function addProjectConfig(tree: Tree, opts: DenoAppNormalizedSchema) {
);
const targets: ProjectConfiguration['targets'] = {
build: {
executor: '@nx/deno:emit',
executor:
opts.bundler === 'deno_emit' ? '@nx/deno:emit' : '@nx/deno:esbuild',
outputs: [
joinPathFragments(
'dist',
Expand Down
1 change: 1 addition & 0 deletions packages/deno/src/generators/application/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface DenoAppGeneratorSchema {
monorepo?: boolean;
rootProject?: boolean;
framework?: 'oak' | 'none';
bundler?: 'esbuild' | 'deno_emit';
}

export interface DenoAppNormalizedSchema extends DenoAppGeneratorSchema {
Expand Down
Loading
Loading