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(js): inline non-buildable libs for tsc and swc #12280

Merged
merged 1 commit into from Oct 7, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/generated/packages/js.json
Expand Up @@ -314,6 +314,19 @@
"description": "When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.",
"enum": ["dependencies", "peerDependencies"],
"default": "peerDependencies"
},
"external": {
"description": "A list projects to be treated as external. This feature is experimental",
"oneOf": [
{ "type": "string", "enum": ["all", "none"] },
{ "type": "array", "items": { "type": "string" } }
]
},
"externalBuildTargets": {
"type": "array",
"items": { "type": "string" },
"description": "List of target names that annotate a build target for a project",
"default": ["build"]
}
},
"required": ["main", "outputPath", "tsConfig"],
Expand Down Expand Up @@ -472,6 +485,19 @@
"description": "When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.",
"enum": ["dependencies", "peerDependencies"],
"default": "peerDependencies"
},
"external": {
"description": "A list projects to be treated as external. This feature is experimental",
"oneOf": [
{ "type": "string", "enum": ["all", "none"] },
{ "type": "array", "items": { "type": "string" } }
]
},
"externalBuildTargets": {
"type": "array",
"items": { "type": "string" },
"description": "List of target names that annotate a build target for a project",
"default": ["build"]
}
},
"required": ["main", "outputPath", "tsConfig"],
Expand Down
115 changes: 115 additions & 0 deletions e2e/js/src/js.test.ts
@@ -1,3 +1,4 @@
import { execSync } from 'child_process';
import { satisfies } from 'semver';
import {
checkFilesDoNotExist,
Expand Down Expand Up @@ -306,6 +307,120 @@ export function ${lib}Wildcard() {
);
}, 120000);

describe('inlining', () => {
it.each(['tsc', 'swc'])(
'should inline libraries with --compiler=%s',
async (compiler) => {
const parent = uniq('parent');
runCLI(`generate @nrwl/js:lib ${parent} --compiler=${compiler}`);

const buildable = uniq('buildable');
runCLI(`generate @nrwl/js:lib ${buildable}`);

const nonBuildable = uniq('nonbuildable');
runCLI(`generate @nrwl/js:lib ${nonBuildable} --buildable=false`);

updateFile(`libs/${parent}/src/lib/${parent}.ts`, () => {
return `
import { ${buildable} } from '@${scope}/${buildable}';
import { ${nonBuildable} } from '@${scope}/${nonBuildable}';

export function ${parent}() {
${buildable}();
${nonBuildable}();
}
`;
});

// 1. external is set to all
execSync(`rm -rf dist`);
runCLI(`build ${parent} --external=all`);
checkFilesExist(
`dist/libs/${buildable}/src/index.js`, // buildable
`dist/libs/${parent}/src/index.js`, // parent
`dist/libs/${parent}/${nonBuildable}/src/index.js` // inlined non buildable
);
// non-buildable lib import path is modified to relative path
let fileContent = readFile(`dist/libs/${parent}/src/lib/${parent}.js`);
expect(fileContent).toContain(`${nonBuildable}/src`);

// 2. external is set to none
execSync(`rm -rf dist`);
runCLI(`build ${parent} --external=none`);
checkFilesExist(
`dist/libs/${parent}/src/index.js`, // parent
`dist/libs/${parent}/${buildable}/src/index.js`, // inlined buildable
`dist/libs/${parent}/${nonBuildable}/src/index.js` // inlined non buildable
);
fileContent = readFile(`dist/libs/${parent}/src/lib/${parent}.js`);
expect(fileContent).toContain(`${nonBuildable}/src`);
expect(fileContent).toContain(`${buildable}/src`);

// 3. external is set to an array of libs
execSync(`rm -rf dist`);
runCLI(`build ${parent} --external=${buildable}`);
checkFilesExist(
`dist/libs/${buildable}/src/index.js`, // buildable
`dist/libs/${parent}/src/index.js`, // parent
`dist/libs/${parent}/${nonBuildable}/src/index.js` // inlined non buildable
);
fileContent = readFile(`dist/libs/${parent}/src/lib/${parent}.js`);
expect(fileContent).toContain(`${nonBuildable}/src`);
expect(fileContent).not.toContain(`${buildable}/src`);
},
120000
);

it('should inline nesting libraries', async () => {
const parent = uniq('parent');
runCLI(`generate @nrwl/js:lib ${parent}`);

const child = uniq('child');
runCLI(`generate @nrwl/js:lib ${child} --buildable=false`);

const grandChild = uniq('grandchild');
runCLI(`generate @nrwl/js:lib ${grandChild} --buildable=false`);

updateFile(`libs/${parent}/src/lib/${parent}.ts`, () => {
return `
import { ${child} } from '@${scope}/${child}';

export function ${parent}() {
${child}();
}
`;
});

updateFile(`libs/${child}/src/lib/${child}.ts`, () => {
return `
import { ${grandChild} } from '@${scope}/${grandChild}';

export function ${child}() {
${grandChild}();
}
`;
});

runCLI(`build ${parent} --external=all`);
checkFilesExist(
`dist/libs/${parent}/src/index.js`, // parent
`dist/libs/${parent}/${child}/src/index.js`, // inlined child
`dist/libs/${parent}/${grandChild}/src/index.js` // inlined grand child
);
// non-buildable lib import path is modified to relative path
const parentFileContent = readFile(
`dist/libs/${parent}/src/lib/${parent}.js`
);
expect(parentFileContent).toContain(`${child}/src`);
expect(parentFileContent).not.toContain(`${grandChild}/src`);

const childFileContent = readFile(
`dist/libs/${parent}/${child}/src/lib/${child}.js`
);
expect(childFileContent).toContain(`${grandChild}/src`);
}, 120000);
});

it('should not create a `.babelrc` file when creating libs with js executors (--compiler=tsc)', () => {
const lib = uniq('lib');
runCLI(
Expand Down
2 changes: 1 addition & 1 deletion e2e/node/src/node.test.ts
Expand Up @@ -455,7 +455,7 @@ describe('nest libraries', function () {
const nestlib = uniq('nestlib');
runCLI(`generate @nrwl/nest:lib ${nestlib} --buildable`);

packageInstall('@nestjs/swagger', undefined, '~5.0.0');
packageInstall('@nestjs/swagger', undefined, '~6.0.0');

updateProjectConfig(nestlib, (config) => {
config.targets.build.options.transformers = [
Expand Down
23 changes: 23 additions & 0 deletions packages/js/src/executors/swc/schema.json
Expand Up @@ -72,6 +72,29 @@
"description": "When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.",
"enum": ["dependencies", "peerDependencies"],
"default": "peerDependencies"
},
"external": {
"description": "A list projects to be treated as external. This feature is experimental",
"oneOf": [
{
"type": "string",
"enum": ["all", "none"]
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"externalBuildTargets": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of target names that annotate a build target for a project",
"default": ["build"]
}
},
"required": ["main", "outputPath", "tsConfig"],
Expand Down
68 changes: 64 additions & 4 deletions packages/js/src/executors/swc/swc.impl.ts
Expand Up @@ -3,21 +3,27 @@ import {
assetGlobsToFiles,
FileInputOutput,
} from '@nrwl/workspace/src/utilities/assets';
import { join, relative, resolve } from 'path';

import { removeSync } from 'fs-extra';
import { dirname, join, relative, resolve } from 'path';
import { copyAssets } from '../../utils/assets';
import { checkDependencies } from '../../utils/check-dependencies';
import {
getHelperDependency,
HelperDependency,
} from '../../utils/compiler-helper-dependency';
import {
handleInliningBuild,
isInlineGraphEmpty,
postProcessInlinedDependencies,
} from '../../utils/inline';
import { copyPackageJson } from '../../utils/package-json';
import {
NormalizedSwcExecutorOptions,
SwcExecutorOptions,
} from '../../utils/schema';
import { compileSwc, compileSwcWatch } from '../../utils/swc/compile-swc';
import { getSwcrcPath } from '../../utils/swc/get-swcrc-path';
import { copyAssets } from '../../utils/assets';
import { copyPackageJson } from '../../utils/package-json';
import { generateTmpSwcrc } from '../../utils/swc/inline';

export function normalizeOptions(
options: SwcExecutorOptions,
Expand All @@ -35,6 +41,20 @@ export function normalizeOptions(
options.watch = false;
}

// TODO: put back when inlining story is more stable
// if (options.external == null) {
// options.external = 'all';
// } else if (Array.isArray(options.external) && options.external.length === 0) {
// options.external = 'none';
// }

if (Array.isArray(options.external) && options.external.length > 0) {
const firstItem = options.external[0];
if (firstItem === 'all' || firstItem === 'none') {
options.external = firstItem;
}
}

const files: FileInputOutput[] = assetGlobsToFiles(
options.assets,
contextRoot,
Expand Down Expand Up @@ -67,6 +87,7 @@ export function normalizeOptions(
root: contextRoot,
sourceRoot,
projectRoot,
originalProjectRoot: projectRoot,
outputPath,
tsConfig: join(contextRoot, options.tsConfig),
swcCliOptions,
Expand Down Expand Up @@ -99,6 +120,32 @@ export async function* swcExecutor(
dependencies.push(swcHelperDependency);
}

const inlineProjectGraph = handleInliningBuild(
context,
options,
options.tsConfig
);

if (!isInlineGraphEmpty(inlineProjectGraph)) {
options.projectRoot = '.'; // set to root of workspace to include other libs for type check

// remap paths for SWC compilation
options.swcCliOptions.srcPath = options.swcCliOptions.swcCwd;
options.swcCliOptions.swcCwd = '.';
options.swcCliOptions.destPath = options.swcCliOptions.destPath
.split('../')
.at(-1)
.concat('/', options.swcCliOptions.srcPath);

// tmp swcrc with dependencies to exclude
// - buildable libraries
// - other libraries that are not dependent on the current project
options.swcCliOptions.swcrcPath = generateTmpSwcrc(
inlineProjectGraph,
options.swcCliOptions.swcrcPath
);
}

if (options.watch) {
let disposeFn: () => void;
process.on('SIGINT', () => disposeFn());
Expand All @@ -113,6 +160,7 @@ export async function* swcExecutor(
},
context
);
removeTmpSwcrc(options.swcCliOptions.swcrcPath);
disposeFn = () => {
assetResult?.stop();
packageJsonResult?.stop();
Expand All @@ -130,8 +178,20 @@ export async function* swcExecutor(
},
context
);
removeTmpSwcrc(options.swcCliOptions.swcrcPath);
postProcessInlinedDependencies(
options.outputPath,
options.originalProjectRoot,
inlineProjectGraph
);
});
}
}

function removeTmpSwcrc(swcrcPath: string) {
if (swcrcPath.startsWith('tmp/')) {
removeSync(dirname(swcrcPath));
}
}

export default swcExecutor;
23 changes: 23 additions & 0 deletions packages/js/src/executors/tsc/schema.json
Expand Up @@ -61,6 +61,29 @@
"description": "When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.",
"enum": ["dependencies", "peerDependencies"],
"default": "peerDependencies"
},
"external": {
"description": "A list projects to be treated as external. This feature is experimental",
"oneOf": [
{
"type": "string",
"enum": ["all", "none"]
},
{
"type": "array",
"items": {
"type": "string"
}
}
]
},
"externalBuildTargets": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of target names that annotate a build target for a project",
"default": ["build"]
}
},
"required": ["main", "outputPath", "tsConfig"],
Expand Down
2 changes: 1 addition & 1 deletion packages/js/src/executors/tsc/tsc.impl.spec.ts
@@ -1,8 +1,8 @@
import { ExecutorContext } from '@nrwl/devkit';
import { ExecutorOptions } from '../../utils/schema';
import {
normalizeOptions,
createTypeScriptCompilationOptions,
normalizeOptions,
} from './tsc.impl';

describe('tscExecutor', () => {
Expand Down