diff --git a/docs/angular/api-angular/executors/ng-packagr-lite.md b/docs/angular/api-angular/executors/ng-packagr-lite.md
index 46b4741641..1a910703c0 100644
--- a/docs/angular/api-angular/executors/ng-packagr-lite.md
+++ b/docs/angular/api-angular/executors/ng-packagr-lite.md
@@ -22,6 +22,12 @@ Possible values: `dependencies`, `peerDependencies`
When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.
+### tailwindConfig
+
+Type: `string`
+
+The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured.
+
### tsConfig
Type: `string`
diff --git a/docs/angular/api-angular/executors/package.md b/docs/angular/api-angular/executors/package.md
index 05d8c7ba7e..c177134d1b 100644
--- a/docs/angular/api-angular/executors/package.md
+++ b/docs/angular/api-angular/executors/package.md
@@ -22,6 +22,12 @@ Possible values: `dependencies`, `peerDependencies`
When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.
+### tailwindConfig
+
+Type: `string`
+
+The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured.
+
### tsConfig
Type: `string`
diff --git a/docs/node/api-angular/executors/ng-packagr-lite.md b/docs/node/api-angular/executors/ng-packagr-lite.md
index 5600146283..8a961af8ba 100644
--- a/docs/node/api-angular/executors/ng-packagr-lite.md
+++ b/docs/node/api-angular/executors/ng-packagr-lite.md
@@ -22,6 +22,12 @@ Possible values: `dependencies`, `peerDependencies`
When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.
+### tailwindConfig
+
+Type: `string`
+
+The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured.
+
### tsConfig
Type: `string`
diff --git a/docs/node/api-angular/executors/package.md b/docs/node/api-angular/executors/package.md
index f1f814f0b2..c0cb8912e7 100644
--- a/docs/node/api-angular/executors/package.md
+++ b/docs/node/api-angular/executors/package.md
@@ -22,6 +22,12 @@ Possible values: `dependencies`, `peerDependencies`
When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.
+### tailwindConfig
+
+Type: `string`
+
+The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured.
+
### tsConfig
Type: `string`
diff --git a/docs/react/api-angular/executors/ng-packagr-lite.md b/docs/react/api-angular/executors/ng-packagr-lite.md
index 5600146283..8a961af8ba 100644
--- a/docs/react/api-angular/executors/ng-packagr-lite.md
+++ b/docs/react/api-angular/executors/ng-packagr-lite.md
@@ -22,6 +22,12 @@ Possible values: `dependencies`, `peerDependencies`
When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.
+### tailwindConfig
+
+Type: `string`
+
+The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured.
+
### tsConfig
Type: `string`
diff --git a/docs/react/api-angular/executors/package.md b/docs/react/api-angular/executors/package.md
index f1f814f0b2..c0cb8912e7 100644
--- a/docs/react/api-angular/executors/package.md
+++ b/docs/react/api-angular/executors/package.md
@@ -22,6 +22,12 @@ Possible values: `dependencies`, `peerDependencies`
When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.
+### tailwindConfig
+
+Type: `string`
+
+The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured.
+
### tsConfig
Type: `string`
diff --git a/e2e/angular-extensions/src/angular-library.test.ts b/e2e/angular-extensions/src/angular-library.test.ts
index 3f75095d5e..01f183076d 100644
--- a/e2e/angular-extensions/src/angular-library.test.ts
+++ b/e2e/angular-extensions/src/angular-library.test.ts
@@ -4,6 +4,8 @@ import {
checkFilesExist,
getSelectedPackageManager,
newProject,
+ packageInstall,
+ readFile,
readJson,
removeProject,
runCLI,
@@ -11,6 +13,7 @@ import {
updateFile,
} from '@nrwl/e2e/utils';
import { names } from '@nrwl/devkit';
+import { join } from 'path';
describe('Angular Package', () => {
['publishable', 'buildable'].forEach((testConfig) => {
@@ -22,7 +25,6 @@ describe('Angular Package', () => {
* /
* parentLib =>
* \
- * \
* childLib2
*
*/
@@ -141,13 +143,7 @@ describe('Angular Package', () => {
afterEach(() => removeProject({ onlyOnCI: true }));
- it('should build the library when it does not have any deps', () => {
- runCLI(`build ${childLib}`);
-
- checkFilesExist(`dist/libs/${childLib}/package.json`);
- });
-
- it('should properly add references to any dependency into the parent package.json', () => {
+ it('should build properly and update the parent package.json with the dependencies', () => {
runCLI(`build ${childLib}`);
runCLI(`build ${childLib2}`);
runCLI(`build ${parentLib}`);
@@ -173,56 +169,281 @@ describe('Angular Package', () => {
describe('Publishable library secondary entry point', () => {
let project: string;
- let parentLib: string;
- let childLib: string;
+ let lib: string;
let entryPoint: string;
beforeEach(() => {
project = newProject();
- parentLib = uniq('parentlib');
- childLib = uniq('childlib');
+ lib = uniq('lib');
entryPoint = uniq('entrypoint');
runCLI(
- `generate @nrwl/angular:lib ${parentLib} --publishable --importPath=@${project}/${parentLib} --no-interactive`
+ `generate @nrwl/angular:lib ${lib} --publishable --importPath=@${project}/${lib} --no-interactive`
);
runCLI(
- `generate @nrwl/angular:lib ${childLib} --publishable --importPath=@${project}/${childLib} --no-interactive`
- );
- runCLI(
- `generate @nrwl/angular:secondary-entry-point --name=${entryPoint} --library=${childLib} --no-interactive`
- );
-
- updateFile(
- `libs/${parentLib}/src/lib/${parentLib}.module.ts`,
- `
- import { NgModule } from '@angular/core';
- import { CommonModule } from '@angular/common';
- import { ${
- names(entryPoint).className
- }Module } from '@${project}/${childLib}/${entryPoint}';
-
- @NgModule({
- imports: [CommonModule, ${names(entryPoint).className}Module],
- })
- export class Lib1Module {}
- `
+ `generate @nrwl/angular:secondary-entry-point --name=${entryPoint} --library=${lib} --no-interactive`
);
});
it('should build successfully', () => {
- const buildOutput = runCLI(`build ${parentLib}`);
+ const buildOutput = runCLI(`build ${lib}`);
expect(buildOutput).toContain(
- `Building entry point '@${project}/${childLib}'`
+ `Building entry point '@${project}/${lib}'`
);
expect(buildOutput).toContain(
- `Building entry point '@${project}/${childLib}/${entryPoint}'`
- );
- expect(buildOutput).toContain(
- `Building entry point '@${project}/${parentLib}'`
+ `Building entry point '@${project}/${lib}/${entryPoint}'`
);
expect(buildOutput).toContain('Running target "build" succeeded');
});
});
+
+ describe('Tailwind support', () => {
+ let projectScope: string;
+ let buildLibExecutorOption: string;
+ let buildLibProjectConfig: string;
+ let buildLibRootConfig: string;
+ let pubLibExecutorOption: string;
+ let pubLibProjectConfig: string;
+ let pubLibRootConfig: string;
+
+ const customTailwindConfigFile = 'custom-tailwind.config.js';
+
+ const spacing = {
+ rootConfig: {
+ sm: '2px',
+ md: '4px',
+ lg: '8px',
+ },
+ projectConfig: {
+ sm: '1px',
+ md: '2px',
+ lg: '4px',
+ },
+ executorOption: {
+ sm: '4px',
+ md: '8px',
+ lg: '16px',
+ },
+ };
+
+ const createWorkspaceTailwindConfigFile = () => {
+ const tailwindConfigFile = 'tailwind.config.js';
+
+ const tailwindConfig = `module.exports = {
+ mode: 'jit',
+ purge: ['./apps/**/*.{html,ts}', './libs/**/*.{html,ts}'],
+ darkMode: false,
+ theme: {
+ spacing: {
+ sm: '${spacing.rootConfig.sm}',
+ md: '${spacing.rootConfig.md}',
+ lg: '${spacing.rootConfig.lg}',
+ },
+ },
+ variants: { extend: {} },
+ plugins: [],
+ };
+ `;
+
+ updateFile(tailwindConfigFile, tailwindConfig);
+ };
+
+ const createTailwindConfigFile = (
+ dir: string,
+ lib: string,
+ libSpacing: typeof spacing['executorOption'],
+ tailwindConfigFile = 'tailwind.config.js'
+ ) => {
+ const tailwindConfigFilePath = join(dir, tailwindConfigFile);
+
+ const tailwindConfig = `const { createGlobPatternsForDependencies } = require('@nrwl/angular/tailwind');
+
+ module.exports = {
+ mode: 'jit',
+ purge: [
+ './libs/${lib}/src/**/*.{html,ts}',
+ ...createGlobPatternsForDependencies(__dirname),
+ ],
+ darkMode: false,
+ theme: {
+ spacing: {
+ sm: '${libSpacing.sm}',
+ md: '${libSpacing.md}',
+ lg: '${libSpacing.lg}',
+ },
+ },
+ variants: { extend: {} },
+ plugins: [],
+ };
+ `;
+
+ updateFile(tailwindConfigFilePath, tailwindConfig);
+ };
+
+ const addTailwindConfigToProject = (lib: string) => {
+ const angularJson = readJson('angular.json');
+ angularJson.projects[
+ lib
+ ].architect.build.options.tailwindConfig = `libs/${lib}/${customTailwindConfigFile}`;
+ updateFile('angular.json', JSON.stringify(angularJson, null, 2));
+ };
+
+ const createLibComponent = (lib: string) => {
+ updateFile(
+ `libs/${lib}/src/lib/foo.component.ts`,
+ `import { Component } from '@angular/core';
+
+ @Component({
+ selector: '${projectScope}-foo',
+ template: '',
+ styles: [\`
+ .custom-btn {
+ @apply m-md p-sm;
+ }
+ \`]
+ })
+ export class FooComponent {}
+ `
+ );
+
+ updateFile(
+ `libs/${lib}/src/lib/${lib}.module.ts`,
+ `import { NgModule } from '@angular/core';
+ import { CommonModule } from '@angular/common';
+ import { FooComponent } from './foo.component';
+
+ @NgModule({
+ imports: [CommonModule],
+ declarations: [FooComponent],
+ exports: [FooComponent],
+ })
+ export class LibModule {}
+ `
+ );
+
+ updateFile(
+ `libs/${lib}/src/index.ts`,
+ `export * from './lib/foo.component';
+ export * from './lib/${lib}.module';
+ `
+ );
+ };
+
+ beforeEach(() => {
+ const projectName = uniq('proj');
+
+ projectScope = newProject({ name: projectName });
+ buildLibExecutorOption = uniq('build-lib-executor-option');
+ buildLibProjectConfig = uniq('build-lib-project-config');
+ buildLibRootConfig = uniq('build-lib-root-config');
+ pubLibExecutorOption = uniq('pub-lib-executor-option');
+ pubLibProjectConfig = uniq('pub-lib-project-config');
+ pubLibRootConfig = uniq('pub-lib-root-config');
+
+ // Install Tailwind required packages.
+ // TODO: Remove this when Tailwind generator is created and used.
+ packageInstall('tailwindcss postcss autoprefixer', projectName, 'latest');
+
+ // Create Tailwind config in the workspace root.
+ createWorkspaceTailwindConfigFile();
+
+ // Setup buildable libs
+
+ // Buildable lib with tailwind config specified in the project config
+ runCLI(
+ `generate @nrwl/angular:lib ${buildLibExecutorOption} --buildable --no-interactive`
+ );
+ createLibComponent(buildLibExecutorOption);
+ createTailwindConfigFile(
+ `libs/${buildLibExecutorOption}`,
+ buildLibExecutorOption,
+ spacing.executorOption,
+ customTailwindConfigFile
+ );
+ addTailwindConfigToProject(buildLibExecutorOption);
+
+ // Buildable lib with tailwind config located in the project root
+ runCLI(
+ `generate @nrwl/angular:lib ${buildLibProjectConfig} --buildable --no-interactive`
+ );
+ createLibComponent(buildLibProjectConfig);
+ createTailwindConfigFile(
+ `libs/${buildLibProjectConfig}`,
+ buildLibProjectConfig,
+ spacing.projectConfig
+ );
+
+ // Buildable lib with tailwind config located in the workspace root
+ runCLI(
+ `generate @nrwl/angular:lib ${buildLibRootConfig} --buildable --no-interactive`
+ );
+ createLibComponent(buildLibRootConfig);
+
+ // Publishable libs
+
+ // Publishable lib with tailwind config specified in the project config
+ runCLI(
+ `generate @nrwl/angular:lib ${pubLibExecutorOption} --publishable --importPath=@${projectScope}/${pubLibExecutorOption} --no-interactive`
+ );
+ createLibComponent(pubLibExecutorOption);
+ createTailwindConfigFile(
+ `libs/${pubLibExecutorOption}`,
+ pubLibExecutorOption,
+ spacing.executorOption,
+ customTailwindConfigFile
+ );
+ addTailwindConfigToProject(pubLibExecutorOption);
+
+ // Publishable lib with tailwind config located in the project root
+ runCLI(
+ `generate @nrwl/angular:lib ${pubLibProjectConfig} --publishable --importPath=@${projectScope}/${pubLibProjectConfig} --no-interactive`
+ );
+ createLibComponent(pubLibProjectConfig);
+ createTailwindConfigFile(
+ `libs/${pubLibProjectConfig}`,
+ pubLibProjectConfig,
+ spacing.projectConfig
+ );
+
+ // Publishable lib with tailwind config located in the workspace root
+ runCLI(
+ `generate @nrwl/angular:lib ${pubLibRootConfig} --publishable --importPath=@${projectScope}/${pubLibRootConfig} --no-interactive`
+ );
+ createLibComponent(pubLibRootConfig);
+ });
+
+ const assertComponentStyles = (
+ lib: string,
+ libSpacing: typeof spacing['executorOption']
+ ) => {
+ const builtComponentContent = readFile(
+ `dist/libs/${lib}/esm2020/lib/foo.component.mjs`
+ );
+ let expectedStylesRegex = new RegExp(
+ `styles: \\[\\"\\.custom\\-btn(\\[_ngcontent\\-%COMP%\\])?{margin:${libSpacing.md};padding:${libSpacing.sm}}(\\\\n)?\\"\\]`
+ );
+ expect(builtComponentContent).toMatch(expectedStylesRegex);
+ };
+
+ it('should build and output the right styles based on the tailwind config', () => {
+ runCLI(`build ${buildLibExecutorOption}`);
+ assertComponentStyles(buildLibExecutorOption, spacing.executorOption);
+
+ runCLI(`build ${buildLibProjectConfig}`);
+ assertComponentStyles(buildLibProjectConfig, spacing.projectConfig);
+
+ runCLI(`build ${buildLibRootConfig}`);
+ assertComponentStyles(buildLibRootConfig, spacing.rootConfig);
+
+ runCLI(`build ${pubLibExecutorOption}`);
+ assertComponentStyles(pubLibExecutorOption, spacing.executorOption);
+
+ runCLI(`build ${pubLibProjectConfig}`);
+ assertComponentStyles(pubLibProjectConfig, spacing.projectConfig);
+
+ runCLI(`build ${pubLibRootConfig}`);
+ assertComponentStyles(pubLibRootConfig, spacing.rootConfig);
+ });
+ });
});
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.di.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.di.ts
index dcba403baf..075f7ddf83 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.di.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.di.ts
@@ -1,11 +1,21 @@
+/**
+ * Adapted from the original ng-packagr source.
+ *
+ * Changes made:
+ * - Use our own compileNgcTransformFactory instead of the one provided by ng-packagr.
+ * - Use NX_STYLESHEET_PROCESSOR instead of STYLESHEET_PROCESSOR.
+ * - Use NX_STYLESHEET_PROCESSOR_TOKEN instead of STYLESHEET_PROCESSOR_TOKEN.
+ * - USE NX_OPTIONS_TOKEN instead of OPTIONS_TOKEN.
+ */
+
import { InjectionToken, Provider } from 'injection-js';
import type { Transform } from 'ng-packagr/lib/graph/transform';
import { provideTransform } from 'ng-packagr/lib/graph/transform.di';
-import { OPTIONS_TOKEN } from 'ng-packagr/lib/ng-package/options.di';
import {
- STYLESHEET_PROCESSOR,
- STYLESHEET_PROCESSOR_TOKEN,
-} from 'ng-packagr/lib/styles/stylesheet-processor.di';
+ NX_STYLESHEET_PROCESSOR,
+ NX_STYLESHEET_PROCESSOR_TOKEN,
+} from '../../styles/stylesheet-processor.di';
+import { NX_OPTIONS_TOKEN } from '../options.di';
import { nxCompileNgcTransformFactory } from './compile-ngc.transform';
export const NX_COMPILE_NGC_TOKEN = new InjectionToken(
@@ -14,9 +24,9 @@ export const NX_COMPILE_NGC_TOKEN = new InjectionToken(
export const NX_COMPILE_NGC_TRANSFORM = provideTransform({
provide: NX_COMPILE_NGC_TOKEN,
useFactory: nxCompileNgcTransformFactory,
- deps: [STYLESHEET_PROCESSOR_TOKEN, OPTIONS_TOKEN],
+ deps: [NX_STYLESHEET_PROCESSOR_TOKEN, NX_OPTIONS_TOKEN],
});
export const NX_COMPILE_NGC_PROVIDERS: Provider[] = [
- STYLESHEET_PROCESSOR,
+ NX_STYLESHEET_PROCESSOR,
NX_COMPILE_NGC_TRANSFORM,
];
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.transform.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.transform.ts
index e134bd3cdf..82dcb6c5da 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.transform.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.transform.ts
@@ -1,8 +1,9 @@
/**
* Adapted from the original ngPackagr source.
*
- * Excludes the ngcc compilation which is not needed
- * since these libraries will be compiled by the ngtsc.
+ * Changes made:
+ * - Use our own StylesheetProcessor files instead of the ones provide by ng-packagr.
+ * - Excludes the ngcc compilation for faster builds.
*/
import type { Transform } from 'ng-packagr/lib/graph/transform';
@@ -11,12 +12,12 @@ import {
isEntryPoint,
isEntryPointInProgress,
} from 'ng-packagr/lib/ng-package/nodes';
-import { NgPackagrOptions } from 'ng-packagr/lib/ng-package/options.di';
-import type { StylesheetProcessor as StylesheetProcessorClass } from 'ng-packagr/lib/styles/stylesheet-processor';
import { setDependenciesTsConfigPaths } from 'ng-packagr/lib/ts/tsconfig';
import * as path from 'path';
import * as ts from 'typescript';
import { compileSourceFiles } from '../../ngc/compile-source-files';
+import { StylesheetProcessor as StylesheetProcessorClass } from '../../styles/stylesheet-processor';
+import { NgPackagrOptions } from '../options.di';
export const nxCompileNgcTransformFactory = (
StylesheetProcessor: typeof StylesheetProcessorClass,
@@ -42,8 +43,10 @@ export const nxCompileNgcTransformFactory = (
basePath,
cssUrl,
styleIncludePaths,
- options.cacheEnabled && options.cacheDirectory
- );
+ options.cacheEnabled && options.cacheDirectory,
+ options.watch,
+ options.tailwindConfig
+ ) as any;
await compileSourceFiles(
graph,
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-package.di.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-package.di.ts
index 92f914f0ac..b1038c6931 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-package.di.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/entry-point/write-package.di.ts
@@ -3,6 +3,7 @@
*
* Changes made:
* - Provide our own writePackageTransform function.
+ * - USE NX_OPTIONS_TOKEN instead of OPTIONS_TOKEN.
*/
import { InjectionToken } from 'injection-js';
@@ -11,7 +12,7 @@ import {
provideTransform,
TransformProvider,
} from 'ng-packagr/lib/graph/transform.di';
-import { OPTIONS_TOKEN } from 'ng-packagr/lib/ng-package/options.di';
+import { NX_OPTIONS_TOKEN } from '../options.di';
import { nxWritePackageTransform } from './write-package.transform';
export const NX_WRITE_PACKAGE_TRANSFORM_TOKEN = new InjectionToken(
@@ -20,5 +21,5 @@ export const NX_WRITE_PACKAGE_TRANSFORM_TOKEN = new InjectionToken(
export const NX_WRITE_PACKAGE_TRANSFORM: TransformProvider = provideTransform({
provide: NX_WRITE_PACKAGE_TRANSFORM_TOKEN,
useFactory: nxWritePackageTransform,
- deps: [OPTIONS_TOKEN],
+ deps: [NX_OPTIONS_TOKEN],
});
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/options.di.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/options.di.ts
new file mode 100644
index 0000000000..bcaaeba992
--- /dev/null
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/options.di.ts
@@ -0,0 +1,47 @@
+/**
+ * Adapted from the original ng-packagr.
+ *
+ * Changes made:
+ * - Use our own options interface to add support for tailwindConfig.
+ */
+
+import * as findCacheDirectory from 'find-cache-dir';
+import { InjectionToken, Provider, ValueProvider } from 'injection-js';
+import { NgPackagrOptions as NgPackagrOptionsBase } from 'ng-packagr/lib/ng-package/options.di';
+import { tmpdir } from 'os';
+import { resolve } from 'path';
+
+export interface NgPackagrOptions extends NgPackagrOptionsBase {
+ tailwindConfig?: string;
+}
+
+export const NX_OPTIONS_TOKEN = new InjectionToken(
+ `nx.v1.options`
+);
+
+export const nxProvideOptions = (
+ options: NgPackagrOptions = {}
+): ValueProvider => ({
+ provide: NX_OPTIONS_TOKEN,
+ useValue: normalizeOptions(options),
+});
+
+export const NX_DEFAULT_OPTIONS_PROVIDER: Provider = nxProvideOptions();
+
+function normalizeOptions(options: NgPackagrOptions = {}) {
+ const ciEnv = process.env['CI'];
+ const isCI = ciEnv?.toLowerCase() === 'true' || ciEnv === '1';
+ const { cacheEnabled = !isCI, cacheDirectory = findCachePath() } = options;
+
+ return {
+ ...options,
+ cacheEnabled,
+ cacheDirectory,
+ };
+}
+
+function findCachePath(): string {
+ const name = 'ng-packagr';
+
+ return findCacheDirectory({ name }) || resolve(tmpdir(), name);
+}
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/package.di.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/package.di.ts
index 117e0a0daf..53c385333e 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/package.di.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/ng-package/package.di.ts
@@ -3,6 +3,8 @@
*
* Changes made:
* - Use NX_ENTRY_POINT_TRANSFORM_TOKEN instead of ENTRY_POINT_TRANSFORM_TOKEN.
+ * - USE NX_OPTIONS_TOKEN instead of OPTIONS_TOKEN.
+ * - USE NX_DEFAULT_OPTIONS_PROVIDER instead of DEFAULT_OPTIONS_PROVIDER.
*/
import type { Provider } from 'injection-js';
@@ -17,13 +19,10 @@ import {
INIT_TS_CONFIG_TOKEN,
INIT_TS_CONFIG_TRANSFORM,
} from 'ng-packagr/lib/ng-package/entry-point/init-tsconfig.di';
-import {
- DEFAULT_OPTIONS_PROVIDER,
- OPTIONS_TOKEN,
-} from 'ng-packagr/lib/ng-package/options.di';
import { packageTransformFactory } from 'ng-packagr/lib/ng-package/package.transform';
import { PROJECT_TOKEN } from 'ng-packagr/lib/project.di';
import { NX_ENTRY_POINT_TRANSFORM_TOKEN } from './entry-point/entry-point.di';
+import { NX_DEFAULT_OPTIONS_PROVIDER, NX_OPTIONS_TOKEN } from './options.di';
export const NX_PACKAGE_TRANSFORM_TOKEN = new InjectionToken(
`nx.v1.packageTransform`
@@ -34,7 +33,7 @@ export const NX_PACKAGE_TRANSFORM = provideTransform({
useFactory: packageTransformFactory,
deps: [
PROJECT_TOKEN,
- OPTIONS_TOKEN,
+ NX_OPTIONS_TOKEN,
INIT_TS_CONFIG_TOKEN,
ANALYSE_SOURCES_TOKEN,
NX_ENTRY_POINT_TRANSFORM_TOKEN,
@@ -43,7 +42,7 @@ export const NX_PACKAGE_TRANSFORM = provideTransform({
export const NX_PACKAGE_PROVIDERS: Provider[] = [
NX_PACKAGE_TRANSFORM,
- DEFAULT_OPTIONS_PROVIDER,
+ NX_DEFAULT_OPTIONS_PROVIDER,
INIT_TS_CONFIG_TRANSFORM,
ANALYSE_SOURCES_TRANSFORM,
];
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/styles/stylesheet-processor.di.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/styles/stylesheet-processor.di.ts
new file mode 100644
index 0000000000..868e90db24
--- /dev/null
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/styles/stylesheet-processor.di.ts
@@ -0,0 +1,18 @@
+/**
+ * Adapted from the original ng-packagr source.
+ *
+ * Changes made:
+ * - Use our own StylesheetProcessor instead of the one provided by ng-packagr.
+ */
+
+import { FactoryProvider, InjectionToken } from 'injection-js';
+import { StylesheetProcessor } from './stylesheet-processor';
+
+export const NX_STYLESHEET_PROCESSOR_TOKEN =
+ new InjectionToken(`nx.v1.stylesheetProcessor`);
+
+export const NX_STYLESHEET_PROCESSOR: FactoryProvider = {
+ provide: NX_STYLESHEET_PROCESSOR_TOKEN,
+ useFactory: () => StylesheetProcessor,
+ deps: [],
+};
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/styles/stylesheet-processor.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/styles/stylesheet-processor.ts
new file mode 100644
index 0000000000..3b02ce742a
--- /dev/null
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-adjustments/styles/stylesheet-processor.ts
@@ -0,0 +1,362 @@
+/**
+ * Adapted from the original ng-packagr source.
+ *
+ * Changes made:
+ * - Added the filePath parameter to the cache key.
+ * - Refactored caching to take into account TailwindCSS processing.
+ * - Added PostCSS plugins needed to support TailwindCSS.
+ * - Added watch mode parameter.
+ */
+
+import * as browserslist from 'browserslist';
+import { sync } from 'find-parent-dir';
+import { existsSync } from 'fs';
+import { EsbuildExecutor } from 'ng-packagr/lib/esbuild/esbuild-executor';
+import {
+ generateKey,
+ readCacheEntry,
+ saveCacheEntry,
+} from 'ng-packagr/lib/utils/cache';
+import * as log from 'ng-packagr/lib/utils/log';
+import { dirname, extname, resolve } from 'path';
+import * as postcssPresetEnv from 'postcss-preset-env';
+import * as postcssUrl from 'postcss-url';
+import {
+ getTailwindPostCssPlugins,
+ getTailwindSetup,
+ tailwindDirectives,
+ TailwindSetup,
+} from '../../../utilities/tailwindcss';
+
+const postcss = require('postcss');
+
+export enum CssUrl {
+ inline = 'inline',
+ none = 'none',
+}
+
+export enum InlineStyleLanguage {
+ sass = 'sass',
+ scss = 'scss',
+ css = 'css',
+ less = 'less',
+}
+
+export interface Result {
+ css: string;
+ warnings: string[];
+ error?: string;
+}
+
+export class StylesheetProcessor {
+ private browserslistData: string[];
+ private targets: string[];
+ private postCssProcessor: ReturnType;
+ private esbuild = new EsbuildExecutor();
+ private tailwindSetup: TailwindSetup | undefined;
+
+ constructor(
+ private readonly basePath: string,
+ private readonly cssUrl?: CssUrl,
+ private readonly styleIncludePaths?: string[],
+ private readonly cacheDirectory?: string | false,
+ private readonly watch?: boolean,
+ private readonly tailwindConfig?: string
+ ) {
+ // By default, browserslist defaults are too inclusive
+ // https://github.com/browserslist/browserslist/blob/83764ea81ffaa39111c204b02c371afa44a4ff07/index.js#L516-L522
+
+ // We change the default query to browsers that Angular support.
+ // https://angular.io/guide/browser-support
+ (browserslist.defaults as string[]) = [
+ 'last 1 Chrome version',
+ 'last 1 Firefox version',
+ 'last 2 Edge major versions',
+ 'last 2 Safari major versions',
+ 'last 2 iOS major versions',
+ 'Firefox ESR',
+ ];
+
+ this.browserslistData = browserslist(undefined, { path: this.basePath });
+ this.targets = transformSupportedBrowsersToTargets(this.browserslistData);
+ this.tailwindSetup = getTailwindSetup(this.basePath, this.tailwindConfig);
+ this.postCssProcessor = this.createPostCssPlugins();
+ }
+
+ async process({
+ filePath,
+ content,
+ }: {
+ filePath: string;
+ content: string;
+ }): Promise {
+ let key: string | undefined;
+
+ if (
+ this.cacheDirectory &&
+ !content.includes('@import') &&
+ !content.includes('@use') &&
+ !this.containsTailwindDirectives(content)
+ ) {
+ // No transitive deps and no Tailwind directives, we can cache more aggressively.
+ key = await generateKey(content, ...this.browserslistData, filePath);
+ const result = await readCacheEntry(this.cacheDirectory, key);
+ if (result) {
+ result.warnings.forEach((msg) => log.warn(msg));
+
+ return result.css;
+ }
+ }
+
+ // Render pre-processor language (sass, styl, less)
+ const renderedCss = await this.renderCss(filePath, content);
+
+ let containsTailwindDirectives = false;
+ if (this.cacheDirectory) {
+ containsTailwindDirectives = this.containsTailwindDirectives(renderedCss);
+ if (!containsTailwindDirectives) {
+ // No Tailwind directives to process by PostCSS, we can return cached results
+ if (!key) {
+ key = await generateKey(
+ renderedCss,
+ ...this.browserslistData,
+ filePath
+ );
+ }
+
+ const cachedResult = await this.getCachedResult(key);
+ if (cachedResult) {
+ return cachedResult;
+ }
+ }
+ }
+
+ // Render postcss (autoprefixing and friends)
+ const result = await this.postCssProcessor.process(renderedCss, {
+ from: filePath,
+ to: filePath.replace(extname(filePath), '.css'),
+ });
+
+ if (this.cacheDirectory && containsTailwindDirectives) {
+ // We had Tailwind directives to process by PostCSS, only now
+ // is safe to return cached results
+ key = await generateKey(result.css, ...this.browserslistData, filePath);
+
+ const cachedResult = await this.getCachedResult(key);
+ if (cachedResult) {
+ return cachedResult;
+ }
+ }
+
+ const warnings = result.warnings().map((w) => w.toString());
+ const { code, warnings: esBuildWarnings } = await this.esbuild.transform(
+ result.css,
+ {
+ loader: 'css',
+ minify: true,
+ target: this.targets,
+ sourcefile: filePath,
+ }
+ );
+
+ if (esBuildWarnings.length > 0) {
+ warnings.push(
+ ...(await this.esbuild.formatMessages(esBuildWarnings, {
+ kind: 'warning',
+ }))
+ );
+ }
+
+ if (this.cacheDirectory) {
+ await saveCacheEntry(
+ this.cacheDirectory,
+ key,
+ JSON.stringify({
+ css: code,
+ warnings,
+ })
+ );
+ }
+ warnings.forEach((msg) => log.warn(msg));
+
+ return code;
+ }
+
+ private async getCachedResult(key: string): Promise {
+ const cachedResult = await readCacheEntry(
+ this.cacheDirectory as string,
+ key
+ );
+ if (cachedResult) {
+ cachedResult.warnings.forEach((msg) => log.warn(msg));
+
+ return cachedResult.css;
+ }
+
+ return undefined;
+ }
+
+ private containsTailwindDirectives(content: string): boolean {
+ return (
+ this.tailwindSetup && tailwindDirectives.some((d) => content.includes(d))
+ );
+ }
+
+ private createPostCssPlugins(): ReturnType {
+ const postCssPlugins = [];
+ if (this.cssUrl !== CssUrl.none) {
+ postCssPlugins.push(postcssUrl({ url: this.cssUrl }));
+ }
+
+ if (this.tailwindSetup) {
+ postCssPlugins.push(
+ ...getTailwindPostCssPlugins(
+ this.tailwindSetup,
+ this.styleIncludePaths,
+ this.watch
+ )
+ );
+ }
+
+ postCssPlugins.push(
+ postcssPresetEnv({
+ browsers: this.browserslistData,
+ autoprefixer: true,
+ stage: 3,
+ })
+ );
+
+ return postcss(postCssPlugins);
+ }
+
+ private async renderCss(filePath: string, css: string): Promise {
+ const ext = extname(filePath);
+
+ switch (ext) {
+ case '.sass':
+ case '.scss': {
+ return (await import('sass'))
+ .renderSync({
+ file: filePath,
+ data: css,
+ indentedSyntax: '.sass' === ext,
+ importer: customSassImporter,
+ includePaths: this.styleIncludePaths,
+ })
+ .css.toString();
+ }
+ case '.less': {
+ const { css: content } = await (
+ await import('less')
+ ).render(css, {
+ filename: filePath,
+ math: 'always',
+ javascriptEnabled: true,
+ paths: this.styleIncludePaths,
+ });
+
+ return content;
+ }
+ case '.styl':
+ case '.stylus': {
+ const stylus = (await import('stylus')).default;
+
+ return (
+ stylus(css)
+ // add paths for resolve
+ .set('paths', [
+ this.basePath,
+ '.',
+ ...this.styleIncludePaths,
+ 'node_modules',
+ ])
+ // add support for resolving plugins from node_modules
+ .set('filename', filePath)
+ // turn on url resolver in stylus, same as flag --resolve-url
+ .set('resolve url', true)
+ .define('url', stylus.resolver(undefined))
+ .render()
+ );
+ }
+ case '.css':
+ default:
+ return css;
+ }
+ }
+}
+
+function transformSupportedBrowsersToTargets(
+ supportedBrowsers: string[]
+): string[] {
+ const transformed: string[] = [];
+
+ // https://esbuild.github.io/api/#target
+ const esBuildSupportedBrowsers = new Set([
+ 'safari',
+ 'firefox',
+ 'edge',
+ 'chrome',
+ 'ios',
+ ]);
+
+ for (const browser of supportedBrowsers) {
+ let [browserName, version] = browser.split(' ');
+
+ // browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios`
+ if (browserName === 'ios_saf') {
+ browserName = 'ios';
+ // browserslist also uses ranges for iOS Safari versions but only the lowest is required
+ // to perform minimum supported feature checks. esbuild also expects a single version.
+ [version] = version.split('-');
+ }
+
+ if (browserName === 'ie') {
+ transformed.push('edge12');
+ } else if (esBuildSupportedBrowsers.has(browserName)) {
+ if (browserName === 'safari' && version === 'TP') {
+ // esbuild only supports numeric versions so `TP` is converted to a high number (999) since
+ // a Technology Preview (TP) of Safari is assumed to support all currently known features.
+ version = '999';
+ }
+
+ transformed.push(browserName + version);
+ }
+ }
+
+ return transformed.length ? transformed : undefined;
+}
+
+function customSassImporter(
+ url: string,
+ prev: string
+): { file: string; prev: string } | undefined {
+ // NB: Sass importer should always be sync as otherwise it will cause
+ // sass to go in the async path which is slower.
+ if (url[0] !== '~') {
+ return undefined;
+ }
+
+ const result = resolveImport(url.substring(1), prev);
+ if (!result) {
+ return undefined;
+ }
+
+ return {
+ file: result,
+ prev,
+ };
+}
+
+function resolveImport(target: string, basePath: string): string | undefined {
+ const root = sync(basePath, 'node_modules');
+ if (!root) {
+ return undefined;
+ }
+
+ const filePath = resolve(root, 'node_modules', target);
+ if (existsSync(filePath) || existsSync(dirname(filePath))) {
+ return filePath;
+ }
+
+ return resolveImport(target, dirname(root));
+}
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-lite.impl.spec.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-lite.impl.spec.ts
index 4fec706def..1da41aa47f 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-lite.impl.spec.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-lite.impl.spec.ts
@@ -1,6 +1,7 @@
jest.mock('@nrwl/workspace/src/core/project-graph');
jest.mock('@nrwl/workspace/src/utilities/buildable-libs-utils');
jest.mock('ng-packagr');
+jest.mock('./ng-packagr-adjustments/ng-package/options.di');
import type { ExecutorContext } from '@nrwl/devkit';
import * as buildableLibsUtils from '@nrwl/workspace/src/utilities/buildable-libs-utils';
@@ -8,11 +9,12 @@ import * as ngPackagr from 'ng-packagr';
import { BehaviorSubject } from 'rxjs';
import type { BuildAngularLibraryExecutorOptions } from '../package/schema';
import { NX_ENTRY_POINT_PROVIDERS } from './ng-packagr-adjustments/ng-package/entry-point/entry-point.di';
+import { nxProvideOptions } from './ng-packagr-adjustments/ng-package/options.di';
import {
NX_PACKAGE_PROVIDERS,
NX_PACKAGE_TRANSFORM,
} from './ng-packagr-adjustments/ng-package/package.di';
-import ngPackagrLiteExecutor from './ng-packagr-lite.impl';
+import { ngPackagrLiteExecutor } from './ng-packagr-lite.impl';
describe('NgPackagrLite executor', () => {
let context: ExecutorContext;
@@ -83,12 +85,24 @@ describe('NgPackagrLite executor', () => {
(
buildableLibsUtils.checkDependentProjectsHaveBeenBuilt as jest.Mock
).mockReturnValue(true);
+ const extraOptions: Partial = {
+ tailwindConfig: 'path/to/tailwind.config.js',
+ watch: false,
+ };
+ const nxProvideOptionsResult = { ...extraOptions, cacheEnabled: true };
+ (nxProvideOptions as jest.Mock).mockImplementation(
+ () => nxProvideOptionsResult
+ );
- const result = await ngPackagrLiteExecutor(options, context).next();
+ const result = await ngPackagrLiteExecutor(
+ { ...options, ...extraOptions },
+ context
+ ).next();
expect(ngPackagr.NgPackagr).toHaveBeenCalledWith([
...NX_PACKAGE_PROVIDERS,
...NX_ENTRY_POINT_PROVIDERS,
+ nxProvideOptionsResult,
]);
expect(ngPackagrWithBuildTransformMock).toHaveBeenCalledWith(
NX_PACKAGE_TRANSFORM.provide
diff --git a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-lite.impl.ts b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-lite.impl.ts
index 584ce2a8da..bb95ef66b3 100644
--- a/packages/angular/src/executors/ng-packagr-lite/ng-packagr-lite.impl.ts
+++ b/packages/angular/src/executors/ng-packagr-lite/ng-packagr-lite.impl.ts
@@ -8,6 +8,7 @@ import { resolve } from 'path';
import { createLibraryExecutor } from '../package/package.impl';
import type { BuildAngularLibraryExecutorOptions } from '../package/schema';
import { NX_ENTRY_POINT_PROVIDERS } from './ng-packagr-adjustments/ng-package/entry-point/entry-point.di';
+import { nxProvideOptions } from './ng-packagr-adjustments/ng-package/options.di';
import {
NX_PACKAGE_PROVIDERS,
NX_PACKAGE_TRANSFORM,
@@ -22,6 +23,10 @@ async function initializeNgPackgrLite(
// Add default providers to this list.
...NX_PACKAGE_PROVIDERS,
...NX_ENTRY_POINT_PROVIDERS,
+ nxProvideOptions({
+ tailwindConfig: options.tailwindConfig,
+ watch: options.watch,
+ }),
]);
packager.forProject(resolve(context.root, options.project));
packager.withBuildTransform(NX_PACKAGE_TRANSFORM.provide);
diff --git a/packages/angular/src/executors/ng-packagr-lite/schema.json b/packages/angular/src/executors/ng-packagr-lite/schema.json
index fc42fedf7b..f1f15dc2ce 100644
--- a/packages/angular/src/executors/ng-packagr-lite/schema.json
+++ b/packages/angular/src/executors/ng-packagr-lite/schema.json
@@ -28,6 +28,10 @@
"description": "When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.",
"enum": ["dependencies", "peerDependencies"],
"default": "peerDependencies"
+ },
+ "tailwindConfig": {
+ "type": "string",
+ "description": "The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured."
}
},
"additionalProperties": false,
diff --git a/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.di.ts b/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.di.ts
index 05493c95b2..a0ea7cf921 100644
--- a/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.di.ts
+++ b/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.di.ts
@@ -13,11 +13,11 @@ import {
provideTransform,
TransformProvider,
} from 'ng-packagr/lib/graph/transform.di';
-import { OPTIONS_TOKEN } from 'ng-packagr/lib/ng-package/options.di';
import {
NX_STYLESHEET_PROCESSOR,
NX_STYLESHEET_PROCESSOR_TOKEN,
} from '../../styles/stylesheet-processor.di';
+import { NX_OPTIONS_TOKEN } from '../options.di';
import { compileNgcTransformFactory } from './compile-ngc.transform';
export const NX_COMPILE_NGC_TOKEN = new InjectionToken(
@@ -27,7 +27,7 @@ export const NX_COMPILE_NGC_TOKEN = new InjectionToken(
export const NX_COMPILE_NGC_TRANSFORM: TransformProvider = provideTransform({
provide: NX_COMPILE_NGC_TOKEN,
useFactory: compileNgcTransformFactory,
- deps: [NX_STYLESHEET_PROCESSOR_TOKEN, OPTIONS_TOKEN],
+ deps: [NX_STYLESHEET_PROCESSOR_TOKEN, NX_OPTIONS_TOKEN],
});
export const NX_COMPILE_NGC_PROVIDERS: Provider[] = [
diff --git a/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.transform.ts b/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.transform.ts
index 2139981fec..7787be3fb7 100644
--- a/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.transform.ts
+++ b/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/entry-point/compile-ngc.transform.ts
@@ -14,7 +14,6 @@ import {
isEntryPoint,
isEntryPointInProgress,
} from 'ng-packagr/lib/ng-package/nodes';
-import { NgPackagrOptions } from 'ng-packagr/lib/ng-package/options.di';
import { compileSourceFiles } from 'ng-packagr/lib/ngc/compile-source-files';
import { NgccProcessor } from 'ng-packagr/lib/ngc/ngcc-processor';
import { setDependenciesTsConfigPaths } from 'ng-packagr/lib/ts/tsconfig';
@@ -23,6 +22,7 @@ import * as ora from 'ora';
import * as path from 'path';
import * as ts from 'typescript';
import { StylesheetProcessor as StylesheetProcessorClass } from '../../styles/stylesheet-processor';
+import { NgPackagrOptions } from '../options.di';
export const compileNgcTransformFactory = (
StylesheetProcessor: typeof StylesheetProcessorClass,
@@ -71,7 +71,8 @@ export const compileNgcTransformFactory = (
cssUrl,
styleIncludePaths,
options.cacheEnabled && options.cacheDirectory,
- options.watch
+ options.watch,
+ options.tailwindConfig
) as any;
await compileSourceFiles(
diff --git a/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/options.di.ts b/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/options.di.ts
new file mode 100644
index 0000000000..bcaaeba992
--- /dev/null
+++ b/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/options.di.ts
@@ -0,0 +1,47 @@
+/**
+ * Adapted from the original ng-packagr.
+ *
+ * Changes made:
+ * - Use our own options interface to add support for tailwindConfig.
+ */
+
+import * as findCacheDirectory from 'find-cache-dir';
+import { InjectionToken, Provider, ValueProvider } from 'injection-js';
+import { NgPackagrOptions as NgPackagrOptionsBase } from 'ng-packagr/lib/ng-package/options.di';
+import { tmpdir } from 'os';
+import { resolve } from 'path';
+
+export interface NgPackagrOptions extends NgPackagrOptionsBase {
+ tailwindConfig?: string;
+}
+
+export const NX_OPTIONS_TOKEN = new InjectionToken(
+ `nx.v1.options`
+);
+
+export const nxProvideOptions = (
+ options: NgPackagrOptions = {}
+): ValueProvider => ({
+ provide: NX_OPTIONS_TOKEN,
+ useValue: normalizeOptions(options),
+});
+
+export const NX_DEFAULT_OPTIONS_PROVIDER: Provider = nxProvideOptions();
+
+function normalizeOptions(options: NgPackagrOptions = {}) {
+ const ciEnv = process.env['CI'];
+ const isCI = ciEnv?.toLowerCase() === 'true' || ciEnv === '1';
+ const { cacheEnabled = !isCI, cacheDirectory = findCachePath() } = options;
+
+ return {
+ ...options,
+ cacheEnabled,
+ cacheDirectory,
+ };
+}
+
+function findCachePath(): string {
+ const name = 'ng-packagr';
+
+ return findCacheDirectory({ name }) || resolve(tmpdir(), name);
+}
diff --git a/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/package.di.ts b/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/package.di.ts
index 0e46a13208..084c807672 100644
--- a/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/package.di.ts
+++ b/packages/angular/src/executors/package/ng-packagr-adjustments/ng-package/package.di.ts
@@ -3,6 +3,8 @@
*
* Changes made:
* - Use NX_ENTRY_POINT_TRANSFORM_TOKEN instead of ENTRY_POINT_TRANSFORM_TOKEN.
+ * - USE NX_OPTIONS_TOKEN instead of OPTIONS_TOKEN.
+ * - USE NX_DEFAULT_OPTIONS_PROVIDER instead of DEFAULT_OPTIONS_PROVIDER.
*/
import { InjectionToken, Provider } from 'injection-js';
@@ -19,13 +21,10 @@ import {
INIT_TS_CONFIG_TOKEN,
INIT_TS_CONFIG_TRANSFORM,
} from 'ng-packagr/lib/ng-package/entry-point/init-tsconfig.di';
-import {
- DEFAULT_OPTIONS_PROVIDER,
- OPTIONS_TOKEN,
-} from 'ng-packagr/lib/ng-package/options.di';
import { packageTransformFactory } from 'ng-packagr/lib/ng-package/package.transform';
import { PROJECT_TOKEN } from 'ng-packagr/lib/project.di';
import { NX_ENTRY_POINT_TRANSFORM_TOKEN } from './entry-point/entry-point.di';
+import { NX_DEFAULT_OPTIONS_PROVIDER, NX_OPTIONS_TOKEN } from './options.di';
export const NX_PACKAGE_TRANSFORM_TOKEN = new InjectionToken(
`nx.v1.packageTransform`
@@ -36,7 +35,7 @@ export const NX_PACKAGE_TRANSFORM: TransformProvider = provideTransform({
useFactory: packageTransformFactory,
deps: [
PROJECT_TOKEN,
- OPTIONS_TOKEN,
+ NX_OPTIONS_TOKEN,
INIT_TS_CONFIG_TOKEN,
ANALYSE_SOURCES_TOKEN,
NX_ENTRY_POINT_TRANSFORM_TOKEN,
@@ -45,7 +44,7 @@ export const NX_PACKAGE_TRANSFORM: TransformProvider = provideTransform({
export const NX_PACKAGE_PROVIDERS: Provider[] = [
NX_PACKAGE_TRANSFORM,
- DEFAULT_OPTIONS_PROVIDER,
+ NX_DEFAULT_OPTIONS_PROVIDER,
INIT_TS_CONFIG_TRANSFORM,
ANALYSE_SOURCES_TRANSFORM,
];
diff --git a/packages/angular/src/executors/package/ng-packagr-adjustments/styles/stylesheet-processor.ts b/packages/angular/src/executors/package/ng-packagr-adjustments/styles/stylesheet-processor.ts
index 387c0dcfaf..3b02ce742a 100644
--- a/packages/angular/src/executors/package/ng-packagr-adjustments/styles/stylesheet-processor.ts
+++ b/packages/angular/src/executors/package/ng-packagr-adjustments/styles/stylesheet-processor.ts
@@ -26,7 +26,7 @@ import {
getTailwindSetup,
tailwindDirectives,
TailwindSetup,
-} from '../utilities/tailwindcss';
+} from '../../../utilities/tailwindcss';
const postcss = require('postcss');
@@ -60,7 +60,8 @@ export class StylesheetProcessor {
private readonly cssUrl?: CssUrl,
private readonly styleIncludePaths?: string[],
private readonly cacheDirectory?: string | false,
- private readonly watch?: boolean
+ private readonly watch?: boolean,
+ private readonly tailwindConfig?: string
) {
// By default, browserslist defaults are too inclusive
// https://github.com/browserslist/browserslist/blob/83764ea81ffaa39111c204b02c371afa44a4ff07/index.js#L516-L522
@@ -78,7 +79,7 @@ export class StylesheetProcessor {
this.browserslistData = browserslist(undefined, { path: this.basePath });
this.targets = transformSupportedBrowsersToTargets(this.browserslistData);
- this.tailwindSetup = getTailwindSetup(this.basePath);
+ this.tailwindSetup = getTailwindSetup(this.basePath, this.tailwindConfig);
this.postCssProcessor = this.createPostCssPlugins();
}
diff --git a/packages/angular/src/executors/package/package.impl.spec.ts b/packages/angular/src/executors/package/package.impl.spec.ts
index 023066f129..31710978fc 100644
--- a/packages/angular/src/executors/package/package.impl.spec.ts
+++ b/packages/angular/src/executors/package/package.impl.spec.ts
@@ -1,17 +1,19 @@
jest.mock('@nrwl/workspace/src/core/project-graph');
jest.mock('@nrwl/workspace/src/utilities/buildable-libs-utils');
jest.mock('ng-packagr');
+jest.mock('./ng-packagr-adjustments/ng-package/options.di');
import type { ExecutorContext } from '@nrwl/devkit';
import * as buildableLibsUtils from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import * as ngPackagr from 'ng-packagr';
import { BehaviorSubject } from 'rxjs';
import { NX_ENTRY_POINT_PROVIDERS } from './ng-packagr-adjustments/ng-package/entry-point/entry-point.di';
+import { nxProvideOptions } from './ng-packagr-adjustments/ng-package/options.di';
import {
NX_PACKAGE_PROVIDERS,
NX_PACKAGE_TRANSFORM,
} from './ng-packagr-adjustments/ng-package/package.di';
-import packageExecutor from './package.impl';
+import { packageExecutor } from './package.impl';
import type { BuildAngularLibraryExecutorOptions } from './schema';
describe('Package executor', () => {
@@ -82,12 +84,24 @@ describe('Package executor', () => {
(
buildableLibsUtils.checkDependentProjectsHaveBeenBuilt as jest.Mock
).mockReturnValue(true);
+ const extraOptions: Partial = {
+ tailwindConfig: 'path/to/tailwind.config.js',
+ watch: false,
+ };
+ const nxProvideOptionsResult = { ...extraOptions, cacheEnabled: true };
+ (nxProvideOptions as jest.Mock).mockImplementation(
+ () => nxProvideOptionsResult
+ );
- const result = await packageExecutor(options, context).next();
+ const result = await packageExecutor(
+ { ...options, ...extraOptions },
+ context
+ ).next();
expect(ngPackagr.NgPackagr).toHaveBeenCalledWith([
...NX_PACKAGE_PROVIDERS,
...NX_ENTRY_POINT_PROVIDERS,
+ nxProvideOptionsResult,
]);
expect(ngPackagrWithBuildTransformMock).toHaveBeenCalledWith(
NX_PACKAGE_TRANSFORM.provide
diff --git a/packages/angular/src/executors/package/package.impl.ts b/packages/angular/src/executors/package/package.impl.ts
index 2c876041d7..c8b3cba34f 100644
--- a/packages/angular/src/executors/package/package.impl.ts
+++ b/packages/angular/src/executors/package/package.impl.ts
@@ -1,12 +1,10 @@
import type { ExecutorContext } from '@nrwl/devkit';
import { readCachedProjectGraph } from '@nrwl/workspace/src/core/project-graph';
-import {
- createTmpTsConfig,
- DependentBuildableProjectNode,
-} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import {
calculateProjectDependencies,
checkDependentProjectsHaveBeenBuilt,
+ createTmpTsConfig,
+ DependentBuildableProjectNode,
updateBuildableProjectPackageJsonDependencies,
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
import type { NgPackagr } from 'ng-packagr';
@@ -15,6 +13,7 @@ import { from } from 'rxjs';
import { eachValueFrom } from 'rxjs-for-await';
import { mapTo, switchMap, tap } from 'rxjs/operators';
import { NX_ENTRY_POINT_PROVIDERS } from './ng-packagr-adjustments/ng-package/entry-point/entry-point.di';
+import { nxProvideOptions } from './ng-packagr-adjustments/ng-package/options.di';
import {
NX_PACKAGE_PROVIDERS,
NX_PACKAGE_TRANSFORM,
@@ -29,6 +28,10 @@ async function initializeNgPackagr(
const packager = new (await import('ng-packagr')).NgPackagr([
...NX_PACKAGE_PROVIDERS,
...NX_ENTRY_POINT_PROVIDERS,
+ nxProvideOptions({
+ tailwindConfig: options.tailwindConfig,
+ watch: options.watch,
+ }),
]);
packager.forProject(resolve(context.root, options.project));
diff --git a/packages/angular/src/executors/package/schema.d.ts b/packages/angular/src/executors/package/schema.d.ts
index a9ab6080d2..abe918b1ac 100644
--- a/packages/angular/src/executors/package/schema.d.ts
+++ b/packages/angular/src/executors/package/schema.d.ts
@@ -1,7 +1,8 @@
export interface BuildAngularLibraryExecutorOptions {
project: string;
- tsConfig?: string;
buildableProjectDepsInPackageJsonType?: 'dependencies' | 'peerDependencies';
+ tailwindConfig?: string;
+ tsConfig?: string;
updateBuildableProjectDepsInPackageJson?: boolean;
watch?: boolean;
}
diff --git a/packages/angular/src/executors/package/schema.json b/packages/angular/src/executors/package/schema.json
index 1fbb9e52d7..212deeece8 100644
--- a/packages/angular/src/executors/package/schema.json
+++ b/packages/angular/src/executors/package/schema.json
@@ -28,6 +28,10 @@
"description": "When `updateBuildableProjectDepsInPackageJson` is `true`, this adds dependencies to either `peerDependencies` or `dependencies`.",
"enum": ["dependencies", "peerDependencies"],
"default": "peerDependencies"
+ },
+ "tailwindConfig": {
+ "type": "string",
+ "description": "The full path for the Tailwind configuration file, relative to the workspace root. If not provided and a `tailwind.config.js` file exists in the project or workspace root, it will be used. Otherwise, Tailwind will not be configured."
}
},
"additionalProperties": false,
diff --git a/packages/angular/src/executors/package/ng-packagr-adjustments/utilities/tailwindcss.ts b/packages/angular/src/executors/utilities/tailwindcss.ts
similarity index 76%
rename from packages/angular/src/executors/package/ng-packagr-adjustments/utilities/tailwindcss.ts
rename to packages/angular/src/executors/utilities/tailwindcss.ts
index 9de25ea672..cd712fa624 100644
--- a/packages/angular/src/executors/package/ng-packagr-adjustments/utilities/tailwindcss.ts
+++ b/packages/angular/src/executors/utilities/tailwindcss.ts
@@ -18,15 +18,21 @@ export const tailwindDirectives = [
'@screen',
];
-export function getTailwindSetup(basePath: string): TailwindSetup | undefined {
- // Try to find TailwindCSS configuration file in the project or workspace root.
- const tailwindConfigFile = 'tailwind.config.js';
- let tailwindConfigPath: string | undefined;
- for (const path of [basePath, appRootPath]) {
- const fullPath = join(path, tailwindConfigFile);
- if (existsSync(fullPath)) {
- tailwindConfigPath = fullPath;
- break;
+export function getTailwindSetup(
+ basePath: string,
+ tailwindConfig?: string
+): TailwindSetup | undefined {
+ let tailwindConfigPath = tailwindConfig;
+
+ if (!tailwindConfigPath) {
+ // Try to find TailwindCSS configuration file in the project or workspace root.
+ const tailwindConfigFile = 'tailwind.config.js';
+ for (const path of [basePath, appRootPath]) {
+ const fullPath = join(path, tailwindConfigFile);
+ if (existsSync(fullPath)) {
+ tailwindConfigPath = fullPath;
+ break;
+ }
}
}