feat(angular): add tailwind support when building publishable libraries (#7285)

This commit is contained in:
Leosvel Pérez Espinosa 2021-10-12 12:32:43 +01:00 committed by GitHub
parent 2dd4299056
commit a00f8e0e5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1067 additions and 11 deletions

View File

@ -184,7 +184,7 @@
"minimatch": "3.0.4",
"next": "^11.1.2",
"next-sitemap": "^1.6.108",
"ng-packagr": "~12.2.0",
"ng-packagr": "~12.2.3",
"ngrx-store-freeze": "0.2.4",
"node-fetch": "^2.6.1",
"npm-run-all": "^4.1.5",
@ -193,6 +193,7 @@
"parse5": "4.0.0",
"postcss": "8.3.0",
"postcss-import": "14.0.2",
"postcss-url": "^10.1.1",
"precise-commits": "1.0.2",
"prettier": "2.3.2",
"pretty-quick": "^3.1.0",

View File

@ -90,6 +90,12 @@
"version": "12.9.0",
"description": "Fixes invalid importPaths for buildable and publishable libs.",
"factory": "./src/migrations/update-12-9-0/update-invalid-import-paths"
},
"add-postcss-import": {
"cli": "nx",
"version": "13.0.0-beta.1",
"description": "Adds postcss-import package if ng-packagr is already installed.",
"factory": "./src/migrations/update-13-0-0/add-postcss-import"
}
},
"packageJsonUpdates": {
@ -880,6 +886,15 @@
"alwaysAddToPackageJson": false
}
}
},
"13.0.0": {
"version": "13.0.0-beta.1",
"packages": {
"ng-packagr": {
"version": "~12.2.3",
"alwaysAddToPackageJson": false
}
}
}
}
}

View File

@ -0,0 +1,240 @@
/**
* Adapted from the original ng-packagr source.
*
* Changes made:
* - Added the filePath parameter to the cache key.
* - Added PostCSS plugins needed to support TailwindCSS.
* - Added watch mode parameter.
*/
import * as browserslist from 'browserslist';
import * as cacache from 'cacache';
import { createHash } from 'crypto';
import * as findCacheDirectory from 'find-cache-dir';
import { EsbuildExecutor } from 'ng-packagr/lib/esbuild/esbuild-executor';
import { readFile } from 'ng-packagr/lib/utils/fs';
import * as log from 'ng-packagr/lib/utils/log';
import { tmpdir } from 'os';
import { extname } from 'path';
import postcss from 'postcss';
import * as postcssPresetEnv from 'postcss-preset-env';
import * as postcssUrl from 'postcss-url';
import { getTailwindPostCssPluginsIfPresent } from '../../utilities/tailwindcss';
export enum CssUrl {
inline = 'inline',
none = 'none',
}
export interface Result {
css: string;
warnings: string[];
error?: string;
}
const cachePath = findCacheDirectory({ name: 'ng-packagr-styles' }) || tmpdir();
const ngPackagrVersion = require('ng-packagr/package.json').version;
export class StylesheetProcessor {
private browserslistData: string[];
private postCssProcessor: ReturnType<typeof postcss>;
private esbuild = new EsbuildExecutor();
constructor(
private readonly basePath: string,
private readonly cssUrl?: CssUrl,
private readonly styleIncludePaths?: string[],
private readonly watch?: boolean
) {
this.browserslistData = browserslist(undefined, { path: this.basePath });
this.postCssProcessor = this.createPostCssPlugins();
}
async process(filePath: string): Promise<string> {
const content = await readFile(filePath, 'utf8');
let key: string | undefined;
if (!content.includes('@import') && !content.includes('@use')) {
// No transitive deps, we can cache more aggressively.
key = generateKey(content, this.browserslistData, filePath);
const result = await readCacheEntry(cachePath, 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);
// We cannot cache CSS re-rendering phase, because a transitive dependency via (@import) can case different CSS output.
// Example a change in a mixin or SCSS variable.
if (!key) {
key = generateKey(renderedCss, this.browserslistData, filePath);
}
const cachedResult = await readCacheEntry(cachePath, key);
if (cachedResult) {
cachedResult.warnings.forEach((msg) => log.warn(msg));
return cachedResult.css;
}
// Render postcss (autoprefixing and friends)
const result = await this.postCssProcessor.process(renderedCss, {
from: filePath,
to: filePath.replace(extname(filePath), '.css'),
});
const warnings = result.warnings().map((w) => w.toString());
const { code, warnings: esBuildWarnings } = await this.esbuild.transform(
result.css,
{
loader: 'css',
minify: true,
sourcefile: filePath,
}
);
if (esBuildWarnings.length > 0) {
warnings.push(
...(await this.esbuild.formatMessages(esBuildWarnings, {
kind: 'warning',
}))
);
}
// Add to cache
await cacache.put(
cachePath,
key,
JSON.stringify({
css: code,
warnings,
})
);
warnings.forEach((msg) => log.warn(msg));
return code;
}
private createPostCssPlugins(): ReturnType<typeof postcss> {
const postCssPlugins = [];
if (this.cssUrl !== CssUrl.none) {
postCssPlugins.push(postcssUrl({ url: this.cssUrl }));
}
postCssPlugins.push(
...getTailwindPostCssPluginsIfPresent(
this.basePath,
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': {
/*
* Please be aware of the few differences in behaviour https://github.com/sass/dart-sass/blob/master/README.md#behavioral-differences-from-ruby-sass
* By default `npm install` will install sass.
* To use node-sass you need to use:
* Npm:
* `npm install node-sass --save-dev`
* Yarn:
* `yarn add node-sass --dev`
*/
let sassCompiler: any | undefined;
try {
sassCompiler = require('node-sass'); // Check if node-sass is explicitly included.
} catch {
sassCompiler = await import('sass');
}
return sassCompiler
.renderSync({
file: filePath,
data: css,
indentedSyntax: '.sass' === ext,
importer: await import('node-sass-tilde-importer'),
includePaths: this.styleIncludePaths,
})
.css.toString();
}
case '.less': {
const { css: content } = await (
await import('less')
).render(css, {
filename: filePath,
javascriptEnabled: true,
paths: this.styleIncludePaths,
});
return content;
}
case '.styl':
case '.stylus': {
const stylus = await import('stylus');
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 generateKey(
content: string,
browserslistData: string[],
filePath: string
): string {
return createHash('sha1')
.update(ngPackagrVersion)
.update(content)
.update(browserslistData.join(''))
.update(filePath)
.digest('hex');
}
async function readCacheEntry(
cachePath: string,
key: string
): Promise<Result | undefined> {
const entry = await cacache.get.info(cachePath, key);
if (entry) {
return JSON.parse(await readFile(entry.path, 'utf8'));
}
return undefined;
}

View File

@ -0,0 +1,36 @@
/**
* 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.
*/
import { InjectionToken, Provider } from 'injection-js';
import { Transform } from 'ng-packagr/lib/graph/transform';
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 { compileNgcTransformFactory } from './compile-ngc.transform';
export const NX_COMPILE_NGC_TOKEN = new InjectionToken<Transform>(
`nx.v1.compileNgcTransform`
);
export const NX_COMPILE_NGC_TRANSFORM: TransformProvider = provideTransform({
provide: NX_COMPILE_NGC_TOKEN,
useFactory: compileNgcTransformFactory,
deps: [NX_STYLESHEET_PROCESSOR_TOKEN, OPTIONS_TOKEN],
});
export const NX_COMPILE_NGC_PROVIDERS: Provider[] = [
NX_STYLESHEET_PROCESSOR,
NX_COMPILE_NGC_TRANSFORM,
];

View File

@ -0,0 +1,136 @@
/**
* Adapted from the original ng-packagr source.
*
* Changes made:
* - Use our own StylesheetProcessor files instead of the ones provide by ng-packagr.
*/
import {
Transform,
transformFromPromise,
} from 'ng-packagr/lib/graph/transform';
import * as ivy from 'ng-packagr/lib/ivy';
import {
EntryPointNode,
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';
import * as ora from 'ora';
import * as path from 'path';
import * as ts from 'typescript';
import { StylesheetProcessor as IvyStylesheetProcessor } from '../../ivy/styles/stylesheet-processor';
import { StylesheetProcessor as StylesheetProcessorClass } from '../../styles/stylesheet-processor';
function isEnabled(variable: string | undefined): variable is string {
return (
typeof variable === 'string' &&
(variable === '1' || variable.toLowerCase() === 'true')
);
}
export const compileNgcTransformFactory = (
StylesheetProcessor: typeof StylesheetProcessorClass,
options: NgPackagrOptions
): Transform => {
return transformFromPromise(async (graph) => {
const spinner = ora({
hideCursor: false,
discardStdin: false,
});
try {
const entryPoint: EntryPointNode = graph.find(isEntryPointInProgress());
const entryPoints: EntryPointNode[] = graph.filter(isEntryPoint);
// Add paths mappings for dependencies
const tsConfig = setDependenciesTsConfigPaths(
entryPoint.data.tsConfig,
entryPoints
);
// Compile TypeScript sources
const { esm2015, declarations } = entryPoint.data.destinationFiles;
const { basePath, cssUrl, styleIncludePaths } =
entryPoint.data.entryPoint;
const { moduleResolutionCache, ngccProcessingCache } = entryPoint.cache;
let ngccProcessor: NgccProcessor | undefined;
if (tsConfig.options.enableIvy !== false) {
spinner.start(
`Compiling with Angular sources in Ivy ${
tsConfig.options.compilationMode || 'full'
} compilation mode.`
);
ngccProcessor = new NgccProcessor(
ngccProcessingCache,
tsConfig.project,
tsConfig.options,
entryPoints
);
if (!entryPoint.data.entryPoint.isSecondaryEntryPoint) {
// Only run the async version of NGCC during the primary entrypoint processing.
await ngccProcessor.process();
}
} else {
spinner.start(
`Compiling with Angular in legacy View Engine compilation mode.`
);
}
if (
tsConfig.options.enableIvy !== false &&
!isEnabled(process.env['NG_BUILD_LIB_LEGACY'])
) {
entryPoint.cache.stylesheetProcessor ??= new IvyStylesheetProcessor(
basePath,
cssUrl,
styleIncludePaths
) as any;
await ivy.compileSourceFiles(
graph,
tsConfig,
moduleResolutionCache,
{
outDir: path.dirname(esm2015),
declarationDir: path.dirname(declarations),
declaration: true,
target: ts.ScriptTarget.ES2015,
},
entryPoint.cache.stylesheetProcessor as any,
ngccProcessor,
options.watch
);
} else {
entryPoint.cache.stylesheetProcessor ??= new StylesheetProcessor(
basePath,
cssUrl,
styleIncludePaths,
options.watch
) as any;
await compileSourceFiles(
graph,
tsConfig,
moduleResolutionCache,
entryPoint.cache.stylesheetProcessor as any,
{
outDir: path.dirname(esm2015),
declarationDir: path.dirname(declarations),
declaration: true,
target: ts.ScriptTarget.ES2015,
},
ngccProcessor
);
}
} catch (error) {
spinner.fail();
throw error;
}
spinner.succeed();
return graph;
});
};

View File

@ -0,0 +1,48 @@
/**
* Adapted from the original ng-packagr source.
*
* Changes made:
* - Use NX_COMPILE_NGC_TOKEN instead of COMPILE_NGC_TOKEN.
* - Use NX_COMPILE_NGC_PROVIDERS instead of COMPILE_NGC_PROVIDERS.
*/
import { InjectionToken, Provider } from 'injection-js';
import { Transform } from 'ng-packagr/lib/graph/transform';
import {
provideTransform,
TransformProvider,
} from 'ng-packagr/lib/graph/transform.di';
import { entryPointTransformFactory } from 'ng-packagr/lib/ng-package/entry-point/entry-point.transform';
import {
WRITE_BUNDLES_TRANSFORM,
WRITE_BUNDLES_TRANSFORM_TOKEN,
} from 'ng-packagr/lib/ng-package/entry-point/write-bundles.di';
import {
WRITE_PACKAGE_TRANSFORM,
WRITE_PACKAGE_TRANSFORM_TOKEN,
} from 'ng-packagr/lib/ng-package/entry-point/write-package.di';
import {
NX_COMPILE_NGC_PROVIDERS,
NX_COMPILE_NGC_TOKEN,
} from './compile-ngc.di';
export const NX_ENTRY_POINT_TRANSFORM_TOKEN = new InjectionToken<Transform>(
`nx.v1.entryPointTransform`
);
export const NX_ENTRY_POINT_TRANSFORM: TransformProvider = provideTransform({
provide: NX_ENTRY_POINT_TRANSFORM_TOKEN,
useFactory: entryPointTransformFactory,
deps: [
NX_COMPILE_NGC_TOKEN,
WRITE_BUNDLES_TRANSFORM_TOKEN,
WRITE_PACKAGE_TRANSFORM_TOKEN,
],
});
export const NX_ENTRY_POINT_PROVIDERS: Provider[] = [
NX_ENTRY_POINT_TRANSFORM,
...NX_COMPILE_NGC_PROVIDERS,
WRITE_BUNDLES_TRANSFORM,
WRITE_PACKAGE_TRANSFORM,
];

View File

@ -0,0 +1,53 @@
/**
* Adapted from the original ng-packagr source.
*
* Changes made:
* - Use NX_ENTRY_POINT_TRANSFORM_TOKEN instead of ENTRY_POINT_TRANSFORM_TOKEN.
*/
import { InjectionToken, Provider } from 'injection-js';
import { Transform } from 'ng-packagr/lib/graph/transform';
import {
provideTransform,
TransformProvider,
} from 'ng-packagr/lib/graph/transform.di';
import {
ANALYSE_SOURCES_TOKEN,
ANALYSE_SOURCES_TRANSFORM,
} from 'ng-packagr/lib/ng-package/entry-point/analyse-sources.di';
import {
INIT_TS_CONFIG_TOKEN,
INIT_TS_CONFIG_TRANSFORM,
provideTsConfig,
} 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';
export const NX_PACKAGE_TRANSFORM_TOKEN = new InjectionToken<Transform>(
`nx.v1.packageTransform`
);
export const NX_PACKAGE_TRANSFORM: TransformProvider = provideTransform({
provide: NX_PACKAGE_TRANSFORM_TOKEN,
useFactory: packageTransformFactory,
deps: [
PROJECT_TOKEN,
OPTIONS_TOKEN,
INIT_TS_CONFIG_TOKEN,
ANALYSE_SOURCES_TOKEN,
NX_ENTRY_POINT_TRANSFORM_TOKEN,
],
});
export const NX_PACKAGE_PROVIDERS: Provider[] = [
NX_PACKAGE_TRANSFORM,
DEFAULT_OPTIONS_PROVIDER,
provideTsConfig(),
INIT_TS_CONFIG_TRANSFORM,
ANALYSE_SOURCES_TRANSFORM,
];

View File

@ -0,0 +1,250 @@
/**
* Adapted from the original ng-packagr source.
*
* Changes made:
* - Added the filePath parameter to the cache key.
* - Added PostCSS plugins needed to support TailwindCSS.
* - Added watch mode option.
*/
import * as cacache from 'cacache';
import { createHash } from 'crypto';
import { EsbuildExecutor } from 'ng-packagr/lib/esbuild/esbuild-executor';
import { readFile } from 'ng-packagr/lib/utils/fs';
import * as path from 'path';
import postcss, { LazyResult } from 'postcss';
import * as postcssPresetEnv from 'postcss-preset-env';
import * as postcssUrl from 'postcss-url';
import { parentPort } from 'worker_threads';
import { getTailwindPostCssPluginsIfPresent } from '../utilities/tailwindcss';
import { CssUrl, WorkerOptions, WorkerResult } from './stylesheet-processor';
const ngPackagrVersion = require('ng-packagr/package.json').version;
async function processCss({
filePath,
browserslistData,
cssUrl,
styleIncludePaths,
basePath,
cachePath,
alwaysUseWasm,
watch,
}: WorkerOptions): Promise<WorkerResult> {
const esbuild = new EsbuildExecutor(alwaysUseWasm);
const content = await readFile(filePath, 'utf8');
let key: string | undefined;
if (!content.includes('@import') && !content.includes('@use')) {
// No transitive deps, we can cache more aggressively.
key = generateKey(content, browserslistData, filePath);
const result = await readCacheEntry(cachePath, key);
if (result) {
return result;
}
}
// Render pre-processor language (sass, styl, less)
const renderedCss = await renderCss(
filePath,
content,
basePath,
styleIncludePaths
);
// We cannot cache CSS re-rendering phase, because a transitive dependency via (@import) can case different CSS output.
// Example a change in a mixin or SCSS variable.
if (!key) {
key = generateKey(renderedCss, browserslistData, filePath);
const cachedResult = await readCacheEntry(cachePath, key);
if (cachedResult) {
return cachedResult;
}
}
// Render postcss (autoprefixing and friends)
const result = await optimizeCss(
filePath,
renderedCss,
browserslistData,
basePath,
styleIncludePaths,
cssUrl,
watch
);
const warnings = result.warnings().map((w) => w.toString());
const { code, warnings: esBuildWarnings } = await esbuild.transform(
result.css,
{
loader: 'css',
minify: true,
sourcefile: filePath,
}
);
if (esBuildWarnings.length > 0) {
warnings.push(
...(await esbuild.formatMessages(esBuildWarnings, { kind: 'warning' }))
);
}
// Add to cache
await cacache.put(
cachePath,
key,
JSON.stringify({
css: code,
warnings,
})
);
return {
css: code,
warnings,
};
}
async function renderCss(
filePath: string,
css: string,
basePath: string,
styleIncludePaths?: string[]
): Promise<string> {
const ext = path.extname(filePath);
switch (ext) {
case '.sass':
case '.scss': {
/*
* Please be aware of the few differences in behaviour https://github.com/sass/dart-sass/blob/master/README.md#behavioral-differences-from-ruby-sass
* By default `npm install` will install sass.
* To use node-sass you need to use:
* Npm:
* `npm install node-sass --save-dev`
* Yarn:
* `yarn add node-sass --dev`
*/
let sassCompiler: any | undefined;
try {
sassCompiler = require('node-sass'); // Check if node-sass is explicitly included.
} catch {
sassCompiler = await import('sass');
}
return sassCompiler
.renderSync({
file: filePath,
data: css,
indentedSyntax: '.sass' === ext,
importer: await import('node-sass-tilde-importer'),
includePaths: styleIncludePaths,
})
.css.toString();
}
case '.less': {
const { css: content } = await (
await import('less')
).render(css, {
filename: filePath,
javascriptEnabled: true,
paths: styleIncludePaths,
});
return content;
}
case '.styl':
case '.stylus': {
const stylus = await import('stylus');
return (
stylus(css)
// add paths for resolve
.set('paths', [basePath, '.', ...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 optimizeCss(
filePath: string,
css: string,
browsers: string[],
basePath: string,
includePaths?: string[],
cssUrl?: CssUrl,
watch?: boolean
): LazyResult {
const postCssPlugins = [];
if (cssUrl !== CssUrl.none) {
postCssPlugins.push(postcssUrl({ url: cssUrl }));
}
postCssPlugins.push(
...getTailwindPostCssPluginsIfPresent(basePath, includePaths, watch)
);
postCssPlugins.push(
postcssPresetEnv({
browsers,
autoprefixer: true,
stage: 3,
})
);
return postcss(postCssPlugins).process(css, {
from: filePath,
to: filePath.replace(path.extname(filePath), '.css'),
});
}
function generateKey(
content: string,
browserslistData: string[],
filePath: string
): string {
return createHash('sha1')
.update(ngPackagrVersion)
.update(content)
.update(browserslistData.join(''))
.update(filePath)
.digest('hex');
}
async function readCacheEntry(
cachePath: string,
key: string
): Promise<WorkerResult | undefined> {
const entry = await cacache.get.info(cachePath, key);
if (entry) {
return JSON.parse(await readFile(entry.path, 'utf8'));
}
return undefined;
}
parentPort.on('message', async ({ signal, port, workerOptions }) => {
try {
const result = await processCss(workerOptions);
port.postMessage({ ...result });
} catch (error) {
port.postMessage({ error: error.message });
} finally {
// Change the value of signal[0] to 1
Atomics.add(signal, 0, 1);
// Unlock the main thread
Atomics.notify(signal, 0);
port.close();
}
});

View File

@ -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: [],
};

View File

@ -0,0 +1,102 @@
/**
* Adapted from the original ng-packagr source.
*
* Changes made:
* - Added watch mode parameter.
*/
import * as browserslist from 'browserslist';
import * as findCacheDirectory from 'find-cache-dir';
import { EsbuildExecutor } from 'ng-packagr/lib/esbuild/esbuild-executor';
import * as log from 'ng-packagr/lib/utils/log';
import { tmpdir } from 'os';
import { join } from 'path';
import { MessageChannel, receiveMessageOnPort, Worker } from 'worker_threads';
export enum CssUrl {
inline = 'inline',
none = 'none',
}
export interface WorkerOptions {
filePath: string;
basePath: string;
browserslistData: string[];
cssUrl?: CssUrl;
styleIncludePaths?: string[];
cachePath: string;
alwaysUseWasm: boolean;
watch?: boolean;
}
export interface WorkerResult {
css: string;
warnings: string[];
error?: string;
}
export class StylesheetProcessor {
private browserslistData: string[] | undefined;
private worker: Worker | undefined;
private readonly cachePath: string;
private alwaysUseWasm = !EsbuildExecutor.hasNativeSupport();
constructor(
private readonly basePath: string,
private readonly cssUrl?: CssUrl,
private readonly styleIncludePaths?: string[],
private readonly watch?: boolean
) {
this.cachePath =
findCacheDirectory({ name: 'ng-packagr-styles' }) || tmpdir();
}
process(filePath: string) {
if (!this.worker) {
this.worker = new Worker(
join(__dirname, './stylesheet-processor-worker.js')
);
}
if (!this.browserslistData) {
this.browserslistData = browserslist(undefined, { path: this.basePath });
}
const workerOptions: WorkerOptions = {
filePath,
basePath: this.basePath,
cssUrl: this.cssUrl,
styleIncludePaths: this.styleIncludePaths,
browserslistData: this.browserslistData,
cachePath: this.cachePath,
alwaysUseWasm: this.alwaysUseWasm,
watch: this.watch,
};
const ioChannel = new MessageChannel();
try {
const signal = new Int32Array(new SharedArrayBuffer(4));
this.worker.postMessage(
{ signal, port: ioChannel.port1, workerOptions },
[ioChannel.port1]
);
// Sleep until signal[0] is 0
Atomics.wait(signal, 0, 0);
const { css, warnings, error } = receiveMessageOnPort(
ioChannel.port2
).message;
if (error) {
throw new Error(error);
}
warnings.forEach((msg) => log.warn(msg));
return css;
} finally {
ioChannel.port1.close();
ioChannel.port2.close();
this.worker.unref();
}
}
}

View File

@ -0,0 +1,60 @@
import { logger } from '@nrwl/devkit';
import { appRootPath } from '@nrwl/tao/src/utils/app-root';
import { existsSync } from 'fs';
import { join, relative } from 'path';
import * as postcssImport from 'postcss-import';
export function getTailwindPostCssPluginsIfPresent(
basePath: string,
includePaths?: string[],
watch?: boolean
) {
// 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;
}
}
// Only load Tailwind CSS plugin if configuration file was found.
if (!tailwindConfigPath) {
return [];
}
let tailwindPackagePath: string | undefined;
try {
tailwindPackagePath = require.resolve('tailwindcss');
} catch {
const relativeTailwindConfigPath = relative(
appRootPath,
tailwindConfigPath
);
logger.warn(
`Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
` but the 'tailwindcss' package is not installed.` +
` To enable Tailwind CSS, please install the 'tailwindcss' package.`
);
return [];
}
if (!tailwindPackagePath) {
return [];
}
if (process.env['TAILWIND_MODE'] === undefined) {
process.env['TAILWIND_MODE'] = watch ? 'watch' : 'build';
}
return [
postcssImport({
addModulesDirectories: includePaths ?? [],
resolve: (url: string) => (url.startsWith('~') ? url.substr(1) : url),
}),
require(tailwindPackagePath)({ config: tailwindConfigPath }),
];
}

View File

@ -16,6 +16,7 @@ describe('Package executor', () => {
let ngPackagrBuildMock: jest.Mock;
let ngPackagerWatchSubject: BehaviorSubject<void>;
let ngPackagrWatchMock: jest.Mock;
let ngPackagrWithBuildTransformMock: jest.Mock;
let ngPackagrWithTsConfigMock: jest.Mock;
let options: BuildAngularLibraryExecutorOptions;
let tsConfig: { options: { paths: { [key: string]: string[] } } };
@ -36,11 +37,13 @@ describe('Package executor', () => {
ngPackagrBuildMock = jest.fn(() => Promise.resolve());
ngPackagerWatchSubject = new BehaviorSubject<void>(undefined);
ngPackagrWatchMock = jest.fn(() => ngPackagerWatchSubject.asObservable());
ngPackagrWithBuildTransformMock = jest.fn();
ngPackagrWithTsConfigMock = jest.fn();
(ngPackagr.ngPackagr as jest.Mock).mockImplementation(() => ({
(ngPackagr.NgPackagr as jest.Mock).mockImplementation(() => ({
build: ngPackagrBuildMock,
forProject: jest.fn(),
watch: ngPackagrWatchMock,
withBuildTransform: ngPackagrWithBuildTransformMock,
withTsConfig: ngPackagrWithTsConfigMock,
}));

View File

@ -13,6 +13,11 @@ import { resolve } from 'path';
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 {
NX_PACKAGE_PROVIDERS,
NX_PACKAGE_TRANSFORM,
} from './ng-packagr-adjustments/ng-package/package.di';
import type { BuildAngularLibraryExecutorOptions } from './schema';
async function initializeNgPackagr(
@ -20,8 +25,13 @@ async function initializeNgPackagr(
context: ExecutorContext,
projectDependencies: DependentBuildableProjectNode[]
): Promise<NgPackagr> {
const packager = (await import('ng-packagr')).ngPackagr();
const packager = new (await import('ng-packagr')).NgPackagr([
...NX_PACKAGE_PROVIDERS,
...NX_ENTRY_POINT_PROVIDERS,
]);
packager.forProject(resolve(context.root, options.project));
packager.withBuildTransform(NX_PACKAGE_TRANSFORM.provide);
if (options.tsConfig) {
// read the tsconfig and modify its path in memory to

View File

@ -69,6 +69,7 @@ describe('lib', () => {
// ASSERT
const packageJson = readJson(appTree, '/package.json');
expect(packageJson.devDependencies['ng-packagr']).toBeUndefined();
expect(packageJson.devDependencies['postcss-import']).toBeUndefined();
});
it('should update package.json when publishable', async () => {
@ -81,6 +82,7 @@ describe('lib', () => {
// ASSERT
const packageJson = readJson(appTree, '/package.json');
expect(packageJson.devDependencies['ng-packagr']).toBeDefined();
expect(packageJson.devDependencies['postcss-import']).toBeDefined();
});
it('should update tsconfig.lib.prod.json when enableIvy', async () => {

View File

@ -1,6 +1,6 @@
import {
addDependenciesToPackageJson,
formatFiles,
getWorkspaceLayout,
installPackagesTask,
moveFilesToNewDirectory,
Tree,
@ -70,6 +70,10 @@ export async function libraryGenerator(host: Tree, schema: Partial<Schema>) {
setStrictMode(host, options);
await addLinting(host, options);
if (options.publishable) {
addDependenciesToPackageJson(host, {}, { 'postcss-import': '^14.0.2' });
}
if (options.standaloneConfig) {
await convertToNxProjectGenerator(host, {
project: options.name,

View File

@ -0,0 +1,30 @@
import { readJson, Tree, updateJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import addPostCssImport from './add-postcss-import';
describe('add-postcss-import migration', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace(2);
});
it('should not add postcss-import when ng-packagr is not installed', () => {
addPostCssImport(tree);
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['postcss-import']).toBeUndefined();
});
it('should add postcss-import when ng-packagr is installed', () => {
updateJson(tree, 'package.json', (json) => {
json.devDependencies['ng-packagr'] = '~12.2.3';
return json;
});
addPostCssImport(tree);
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies['postcss-import']).toBeDefined();
});
});

View File

@ -0,0 +1,25 @@
import {
addDependenciesToPackageJson,
formatFiles,
readJson,
Tree,
} from '@nrwl/devkit';
export default async function (tree: Tree) {
const { devDependencies } = readJson(tree, 'package.json');
// Don't add if ng-packagr is not installed
if (!devDependencies['ng-packagr']) {
return;
}
const task = addDependenciesToPackageJson(
tree,
{},
{ 'postcss-import': '^14.0.2' }
);
await formatFiles(tree);
return task;
}

View File

@ -15,10 +15,26 @@ const IGNORE_MATCHES = {
'@ngrx/router-store',
'@ngrx/store',
'@storybook/angular',
'injection-js',
'ng-packagr',
'rxjs',
'semver',
// installed dynamically by the library generator
'ng-packagr',
// ng-packagr deps, some are handled if not installed
'injection-js',
'browserslist',
'cacache',
'find-cache-dir',
'less',
'node-sass',
'node-sass-tilde-importer',
'ora',
'postcss',
'postcss-import',
'postcss-preset-env',
'postcss-url',
'sass',
'stylus',
'tailwindcss',
],
cli: ['@nrwl/cli'],
cypress: ['cypress', '@angular-devkit/schematics', '@nrwl/cypress'],

View File

@ -11813,6 +11813,11 @@ es6-weak-map@^2.0.3:
es6-iterator "^2.0.3"
es6-symbol "^3.1.1"
esbuild-wasm@^0.12.15:
version "0.12.29"
resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.12.29.tgz#1d210bb7d463b2ca51c54d69bb4192d9709f6100"
integrity sha512-amSuB/qOGnTFYLOxGHDGosQbOKZnrinniPHFf6ZxzeNH7WAjLkjXluKyKAtX2YuhTkUXm9XV9igl13iqYZ44fQ==
esbuild@0.12.17:
version "0.12.17"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.17.tgz#5816f905c2905de0ebbc658860df7b5b48afbcd3"
@ -18358,10 +18363,10 @@ next@^11.1.2:
"@next/swc-linux-x64-gnu" "11.1.2"
"@next/swc-win32-x64-msvc" "11.1.2"
ng-packagr@~12.2.0:
version "12.2.0"
resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-12.2.0.tgz#53fe47391b5ddaf5f2c24eaecb23d8a10235d887"
integrity sha512-M/qq78Gb4q13t6SFX70W2DrPxyooSkLwXzhWozjD8yWGihx4q+54a72ODGx7jIrB4fQgrGDcMUTM7t1zGYir8Q==
ng-packagr@~12.2.3:
version "12.2.3"
resolved "https://registry.yarnpkg.com/ng-packagr/-/ng-packagr-12.2.3.tgz#1597f8e70a78ab88bb395675358942bfc14cbc07"
integrity sha512-2KHoglc7UgJMnzkytzZ1wU+IEkb6UrxoU4QZxnF5BSFh9vjUra2nCXH+EKkvxD3WTj0ikXmNIMeNWbwoZpGkgA==
dependencies:
"@rollup/plugin-commonjs" "^20.0.0"
"@rollup/plugin-json" "^4.1.0"
@ -18373,7 +18378,7 @@ ng-packagr@~12.2.0:
chokidar "^3.5.1"
commander "^8.0.0"
dependency-graph "^0.11.0"
esbuild "^0.12.15"
esbuild-wasm "^0.12.15"
find-cache-dir "^3.3.1"
glob "^7.1.6"
injection-js "^2.4.0"
@ -18389,6 +18394,8 @@ ng-packagr@~12.2.0:
rxjs "^6.5.0"
sass "^1.32.8"
stylus "^0.54.8"
optionalDependencies:
esbuild "^0.12.15"
ngrx-store-freeze@0.2.4:
version "0.2.4"