feat(angular): add tailwind support for buildable libraries (#7961)
This commit is contained in:
parent
56aaeb7931
commit
9a08a83591
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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: '<button class="custom-btn">Click me!</button>',
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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<Transform>(
|
||||
@ -14,9 +24,9 @@ export const NX_COMPILE_NGC_TOKEN = new InjectionToken<Transform>(
|
||||
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,
|
||||
];
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<Transform>(
|
||||
@ -20,5 +21,5 @@ export const NX_WRITE_PACKAGE_TRANSFORM_TOKEN = new InjectionToken<Transform>(
|
||||
export const NX_WRITE_PACKAGE_TRANSFORM: TransformProvider = provideTransform({
|
||||
provide: NX_WRITE_PACKAGE_TRANSFORM_TOKEN,
|
||||
useFactory: nxWritePackageTransform,
|
||||
deps: [OPTIONS_TOKEN],
|
||||
deps: [NX_OPTIONS_TOKEN],
|
||||
});
|
||||
|
||||
@ -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<NgPackagrOptions>(
|
||||
`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);
|
||||
}
|
||||
@ -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<Transform>(
|
||||
`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,
|
||||
];
|
||||
|
||||
@ -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<StylesheetProcessor>(`nx.v1.stylesheetProcessor`);
|
||||
|
||||
export const NX_STYLESHEET_PROCESSOR: FactoryProvider = {
|
||||
provide: NX_STYLESHEET_PROCESSOR_TOKEN,
|
||||
useFactory: () => StylesheetProcessor,
|
||||
deps: [],
|
||||
};
|
||||
@ -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<typeof postcss>;
|
||||
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<string> {
|
||||
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<string | undefined> {
|
||||
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<typeof postcss> {
|
||||
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<string> {
|
||||
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));
|
||||
}
|
||||
@ -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<BuildAngularLibraryExecutorOptions> = {
|
||||
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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<Transform>(
|
||||
@ -27,7 +27,7 @@ export const NX_COMPILE_NGC_TOKEN = new InjectionToken<Transform>(
|
||||
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[] = [
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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<NgPackagrOptions>(
|
||||
`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);
|
||||
}
|
||||
@ -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<Transform>(
|
||||
`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,
|
||||
];
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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<BuildAngularLibraryExecutorOptions> = {
|
||||
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
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
export interface BuildAngularLibraryExecutorOptions {
|
||||
project: string;
|
||||
tsConfig?: string;
|
||||
buildableProjectDepsInPackageJsonType?: 'dependencies' | 'peerDependencies';
|
||||
tailwindConfig?: string;
|
||||
tsConfig?: string;
|
||||
updateBuildableProjectDepsInPackageJson?: boolean;
|
||||
watch?: boolean;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user