fix(angular): fix tailwind css support in libraries using components with inline styles (#8393)
This commit is contained in:
parent
77529a1770
commit
80f20db51a
@ -12,390 +12,387 @@ import {
|
||||
} from '@nrwl/e2e/utils';
|
||||
|
||||
describe('Tailwind support', () => {
|
||||
it('tests are disabled', () => {
|
||||
expect(1).toEqual(1);
|
||||
let project: string;
|
||||
|
||||
const defaultButtonBgColor = 'bg-blue-700';
|
||||
|
||||
const buildLibWithTailwind = {
|
||||
name: uniq('build-lib-with-tailwind'),
|
||||
buttonBgColor: 'bg-green-800',
|
||||
};
|
||||
const pubLibWithTailwind = {
|
||||
name: uniq('pub-lib-with-tailwind'),
|
||||
buttonBgColor: 'bg-red-900',
|
||||
};
|
||||
|
||||
const spacing = {
|
||||
root: {
|
||||
sm: '2px',
|
||||
md: '4px',
|
||||
lg: '8px',
|
||||
},
|
||||
projectVariant1: {
|
||||
sm: '1px',
|
||||
md: '2px',
|
||||
lg: '4px',
|
||||
},
|
||||
projectVariant2: {
|
||||
sm: '4px',
|
||||
md: '8px',
|
||||
lg: '16px',
|
||||
},
|
||||
projectVariant3: {
|
||||
sm: '8px',
|
||||
md: '16px',
|
||||
lg: '32px',
|
||||
},
|
||||
};
|
||||
|
||||
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.root.sm}',
|
||||
md: '${spacing.root.md}',
|
||||
lg: '${spacing.root.lg}',
|
||||
},
|
||||
},
|
||||
variants: { extend: {} },
|
||||
plugins: [],
|
||||
};
|
||||
`;
|
||||
|
||||
updateFile(tailwindConfigFile, tailwindConfig);
|
||||
};
|
||||
|
||||
const createTailwindConfigFile = (
|
||||
tailwindConfigFile = 'tailwind.config.js',
|
||||
libSpacing: typeof spacing['projectVariant1']
|
||||
) => {
|
||||
const tailwindConfig = `const { createGlobPatternsForDependencies } = require('@nrwl/angular/tailwind');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'jit',
|
||||
purge: [
|
||||
join(__dirname, 'src/**/*.{html,ts}'),
|
||||
...createGlobPatternsForDependencies(__dirname),
|
||||
],
|
||||
darkMode: false,
|
||||
theme: {
|
||||
spacing: {
|
||||
sm: '${libSpacing.sm}',
|
||||
md: '${libSpacing.md}',
|
||||
lg: '${libSpacing.lg}',
|
||||
},
|
||||
},
|
||||
variants: { extend: {} },
|
||||
plugins: [],
|
||||
};
|
||||
`;
|
||||
|
||||
updateFile(tailwindConfigFile, tailwindConfig);
|
||||
};
|
||||
|
||||
const updateTailwindConfig = (
|
||||
tailwindConfigPath: string,
|
||||
projectSpacing: typeof spacing['root']
|
||||
) => {
|
||||
const tailwindConfig = readFile(tailwindConfigPath);
|
||||
|
||||
const tailwindConfigUpdated = tailwindConfig.replace(
|
||||
'theme: {',
|
||||
`theme: {
|
||||
spacing: {
|
||||
sm: '${projectSpacing.sm}',
|
||||
md: '${projectSpacing.md}',
|
||||
lg: '${projectSpacing.lg}',
|
||||
},`
|
||||
);
|
||||
|
||||
updateFile(tailwindConfigPath, tailwindConfigUpdated);
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
project = newProject();
|
||||
|
||||
// Create tailwind config in the workspace root
|
||||
createWorkspaceTailwindConfigFile();
|
||||
});
|
||||
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
describe('Libraries', () => {
|
||||
const createLibComponent = (
|
||||
lib: string,
|
||||
buttonBgColor: string = defaultButtonBgColor
|
||||
) => {
|
||||
updateFile(
|
||||
`libs/${lib}/src/lib/foo.component.ts`,
|
||||
`import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: '${project}-foo',
|
||||
template: '<button class="custom-btn text-white ${buttonBgColor}">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';
|
||||
`
|
||||
);
|
||||
};
|
||||
|
||||
const assertLibComponentStyles = (
|
||||
lib: string,
|
||||
libSpacing: typeof spacing['root']
|
||||
) => {
|
||||
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 generate a buildable library with tailwind and build correctly', () => {
|
||||
runCLI(
|
||||
`generate @nrwl/angular:lib ${buildLibWithTailwind.name} --buildable --add-tailwind --no-interactive`
|
||||
);
|
||||
updateTailwindConfig(
|
||||
`libs/${buildLibWithTailwind.name}/tailwind.config.js`,
|
||||
spacing.projectVariant1
|
||||
);
|
||||
createLibComponent(
|
||||
buildLibWithTailwind.name,
|
||||
buildLibWithTailwind.buttonBgColor
|
||||
);
|
||||
|
||||
runCLI(`build ${buildLibWithTailwind.name}`);
|
||||
|
||||
assertLibComponentStyles(
|
||||
buildLibWithTailwind.name,
|
||||
spacing.projectVariant1
|
||||
);
|
||||
});
|
||||
|
||||
it('should set up tailwind in a previously generated buildable library and build correctly', () => {
|
||||
const buildLibSetupTailwind = uniq('build-lib-setup-tailwind');
|
||||
runCLI(
|
||||
`generate @nrwl/angular:lib ${buildLibSetupTailwind} --buildable --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:setup-tailwind ${buildLibSetupTailwind} --no-interactive`
|
||||
);
|
||||
updateTailwindConfig(
|
||||
`libs/${buildLibSetupTailwind}/tailwind.config.js`,
|
||||
spacing.projectVariant2
|
||||
);
|
||||
createLibComponent(buildLibSetupTailwind);
|
||||
|
||||
runCLI(`build ${buildLibSetupTailwind}`);
|
||||
|
||||
assertLibComponentStyles(buildLibSetupTailwind, spacing.projectVariant2);
|
||||
});
|
||||
|
||||
it('should correctly build a buildable library with a tailwind.config.js file in the project root or workspace root', () => {
|
||||
const buildLibNoProjectConfig = uniq('build-lib-no-project-config');
|
||||
runCLI(
|
||||
`generate @nrwl/angular:lib ${buildLibNoProjectConfig} --buildable --no-interactive`
|
||||
);
|
||||
createTailwindConfigFile(
|
||||
`libs/${buildLibNoProjectConfig}/tailwind.config.js`,
|
||||
spacing.projectVariant3
|
||||
);
|
||||
createLibComponent(buildLibNoProjectConfig);
|
||||
|
||||
runCLI(`build ${buildLibNoProjectConfig}`);
|
||||
|
||||
assertLibComponentStyles(
|
||||
buildLibNoProjectConfig,
|
||||
spacing.projectVariant3
|
||||
);
|
||||
|
||||
// remove tailwind.config.js file from the project root to test the one in the workspace root
|
||||
removeFile(`libs/${buildLibNoProjectConfig}/tailwind.config.js`);
|
||||
|
||||
runCLI(`build ${buildLibNoProjectConfig}`);
|
||||
|
||||
assertLibComponentStyles(buildLibNoProjectConfig, spacing.root);
|
||||
});
|
||||
|
||||
it('should generate a publishable library with tailwind and build correctly', () => {
|
||||
runCLI(
|
||||
`generate @nrwl/angular:lib ${pubLibWithTailwind.name} --publishable --add-tailwind --importPath=@${project}/${pubLibWithTailwind.name} --no-interactive`
|
||||
);
|
||||
updateTailwindConfig(
|
||||
`libs/${pubLibWithTailwind.name}/tailwind.config.js`,
|
||||
spacing.projectVariant1
|
||||
);
|
||||
createLibComponent(
|
||||
pubLibWithTailwind.name,
|
||||
pubLibWithTailwind.buttonBgColor
|
||||
);
|
||||
|
||||
runCLI(`build ${pubLibWithTailwind.name}`);
|
||||
|
||||
assertLibComponentStyles(
|
||||
pubLibWithTailwind.name,
|
||||
spacing.projectVariant1
|
||||
);
|
||||
});
|
||||
|
||||
it('should set up tailwind in a previously generated publishable library and build correctly', () => {
|
||||
const pubLibSetupTailwind = uniq('pub-lib-setup-tailwind');
|
||||
runCLI(
|
||||
`generate @nrwl/angular:lib ${pubLibSetupTailwind} --publishable --importPath=@${project}/${pubLibSetupTailwind} --no-interactive`
|
||||
);
|
||||
runCLI(
|
||||
`generate @nrwl/angular:setup-tailwind ${pubLibSetupTailwind} --no-interactive`
|
||||
);
|
||||
updateTailwindConfig(
|
||||
`libs/${pubLibSetupTailwind}/tailwind.config.js`,
|
||||
spacing.projectVariant2
|
||||
);
|
||||
createLibComponent(pubLibSetupTailwind);
|
||||
|
||||
runCLI(`build ${pubLibSetupTailwind}`);
|
||||
|
||||
assertLibComponentStyles(pubLibSetupTailwind, spacing.projectVariant2);
|
||||
});
|
||||
|
||||
it('should correctly build a publishable library with a tailwind.config.js file in the project root or workspace root', () => {
|
||||
const pubLibNoProjectConfig = uniq('pub-lib-no-project-config');
|
||||
runCLI(
|
||||
`generate @nrwl/angular:lib ${pubLibNoProjectConfig} --publishable --importPath=@${project}/${pubLibNoProjectConfig} --no-interactive`
|
||||
);
|
||||
createTailwindConfigFile(
|
||||
`libs/${pubLibNoProjectConfig}/tailwind.config.js`,
|
||||
spacing.projectVariant3
|
||||
);
|
||||
createLibComponent(pubLibNoProjectConfig);
|
||||
|
||||
runCLI(`build ${pubLibNoProjectConfig}`);
|
||||
|
||||
assertLibComponentStyles(pubLibNoProjectConfig, spacing.projectVariant3);
|
||||
|
||||
// remove tailwind.config.js file from the project root to test the one in the workspace root
|
||||
removeFile(`libs/${pubLibNoProjectConfig}/tailwind.config.js`);
|
||||
|
||||
runCLI(`build ${pubLibNoProjectConfig}`);
|
||||
|
||||
assertLibComponentStyles(pubLibNoProjectConfig, spacing.root);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Applications', () => {
|
||||
const updateAppComponent = (app: string) => {
|
||||
updateFile(
|
||||
`apps/${app}/src/app/app.component.html`,
|
||||
`<button class="custom-btn text-white">Click me!</button>`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`apps/${app}/src/app/app.component.css`,
|
||||
`.custom-btn {
|
||||
@apply m-md p-sm;
|
||||
}`
|
||||
);
|
||||
};
|
||||
|
||||
const readAppStylesBundle = (app: string) => {
|
||||
const stylesBundlePath = listFiles(`dist/apps/${app}`).find((file) =>
|
||||
file.startsWith('styles.')
|
||||
);
|
||||
const stylesBundle = readFile(`dist/apps/${app}/${stylesBundlePath}`);
|
||||
|
||||
return stylesBundle;
|
||||
};
|
||||
|
||||
const assertAppComponentStyles = (
|
||||
app: string,
|
||||
appSpacing: typeof spacing['root']
|
||||
) => {
|
||||
const mainBundlePath = listFiles(`dist/apps/${app}`).find((file) =>
|
||||
file.startsWith('main.')
|
||||
);
|
||||
const mainBundle = readFile(`dist/apps/${app}/${mainBundlePath}`);
|
||||
let expectedStylesRegex = new RegExp(
|
||||
`styles:\\[\\"\\.custom\\-btn\\[_ngcontent\\-%COMP%\\]{margin:${appSpacing.md};padding:${appSpacing.sm}}\\"\\]`
|
||||
);
|
||||
|
||||
expect(mainBundle).toMatch(expectedStylesRegex);
|
||||
};
|
||||
|
||||
it('should build correctly and only output the tailwind utilities used', () => {
|
||||
const appWithTailwind = uniq('app-with-tailwind');
|
||||
runCLI(
|
||||
`generate @nrwl/angular:app ${appWithTailwind} --add-tailwind --no-interactive`
|
||||
);
|
||||
updateTailwindConfig(
|
||||
`apps/${appWithTailwind}/tailwind.config.js`,
|
||||
spacing.projectVariant1
|
||||
);
|
||||
updateFile(
|
||||
`apps/${appWithTailwind}/src/app/app.module.ts`,
|
||||
`import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { LibModule as LibModule1 } from '@${project}/${buildLibWithTailwind.name}';
|
||||
import { LibModule as LibModule2 } from '@${project}/${pubLibWithTailwind.name}';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule, LibModule1, LibModule2],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
`
|
||||
);
|
||||
updateAppComponent(appWithTailwind);
|
||||
|
||||
runCLI(`build ${appWithTailwind}`);
|
||||
|
||||
assertAppComponentStyles(appWithTailwind, spacing.projectVariant1);
|
||||
let stylesBundle = readAppStylesBundle(appWithTailwind);
|
||||
expect(stylesBundle).toContain('.text-white');
|
||||
expect(stylesBundle).not.toContain('.text-black');
|
||||
expect(stylesBundle).toContain(`.${buildLibWithTailwind.buttonBgColor}`);
|
||||
expect(stylesBundle).toContain(`.${pubLibWithTailwind.buttonBgColor}`);
|
||||
expect(stylesBundle).not.toContain(`.${defaultButtonBgColor}`);
|
||||
});
|
||||
});
|
||||
// let project: string;
|
||||
//
|
||||
// const defaultButtonBgColor = 'bg-blue-700';
|
||||
//
|
||||
// const buildLibWithTailwind = {
|
||||
// name: uniq('build-lib-with-tailwind'),
|
||||
// buttonBgColor: 'bg-green-800',
|
||||
// };
|
||||
// const pubLibWithTailwind = {
|
||||
// name: uniq('pub-lib-with-tailwind'),
|
||||
// buttonBgColor: 'bg-red-900',
|
||||
// };
|
||||
//
|
||||
// const spacing = {
|
||||
// root: {
|
||||
// sm: '2px',
|
||||
// md: '4px',
|
||||
// lg: '8px',
|
||||
// },
|
||||
// projectVariant1: {
|
||||
// sm: '1px',
|
||||
// md: '2px',
|
||||
// lg: '4px',
|
||||
// },
|
||||
// projectVariant2: {
|
||||
// sm: '4px',
|
||||
// md: '8px',
|
||||
// lg: '16px',
|
||||
// },
|
||||
// projectVariant3: {
|
||||
// sm: '8px',
|
||||
// md: '16px',
|
||||
// lg: '32px',
|
||||
// },
|
||||
// };
|
||||
//
|
||||
// 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.root.sm}',
|
||||
// md: '${spacing.root.md}',
|
||||
// lg: '${spacing.root.lg}',
|
||||
// },
|
||||
// },
|
||||
// variants: { extend: {} },
|
||||
// plugins: [],
|
||||
// };
|
||||
// `;
|
||||
//
|
||||
// updateFile(tailwindConfigFile, tailwindConfig);
|
||||
// };
|
||||
//
|
||||
// const createTailwindConfigFile = (
|
||||
// tailwindConfigFile = 'tailwind.config.js',
|
||||
// libSpacing: typeof spacing['projectVariant1']
|
||||
// ) => {
|
||||
// const tailwindConfig = `const { createGlobPatternsForDependencies } = require('@nrwl/angular/tailwind');
|
||||
// const { join } = require('path');
|
||||
//
|
||||
// module.exports = {
|
||||
// mode: 'jit',
|
||||
// purge: [
|
||||
// join(__dirname, 'src/**/*.{html,ts}'),
|
||||
// ...createGlobPatternsForDependencies(__dirname),
|
||||
// ],
|
||||
// darkMode: false,
|
||||
// theme: {
|
||||
// spacing: {
|
||||
// sm: '${libSpacing.sm}',
|
||||
// md: '${libSpacing.md}',
|
||||
// lg: '${libSpacing.lg}',
|
||||
// },
|
||||
// },
|
||||
// variants: { extend: {} },
|
||||
// plugins: [],
|
||||
// };
|
||||
// `;
|
||||
//
|
||||
// updateFile(tailwindConfigFile, tailwindConfig);
|
||||
// };
|
||||
//
|
||||
// const updateTailwindConfig = (
|
||||
// tailwindConfigPath: string,
|
||||
// projectSpacing: typeof spacing['root']
|
||||
// ) => {
|
||||
// const tailwindConfig = readFile(tailwindConfigPath);
|
||||
//
|
||||
// const tailwindConfigUpdated = tailwindConfig.replace(
|
||||
// 'theme: {',
|
||||
// `theme: {
|
||||
// spacing: {
|
||||
// sm: '${projectSpacing.sm}',
|
||||
// md: '${projectSpacing.md}',
|
||||
// lg: '${projectSpacing.lg}',
|
||||
// },`
|
||||
// );
|
||||
//
|
||||
// updateFile(tailwindConfigPath, tailwindConfigUpdated);
|
||||
// };
|
||||
//
|
||||
// beforeAll(() => {
|
||||
// project = newProject();
|
||||
//
|
||||
// // Create tailwind config in the workspace root
|
||||
// createWorkspaceTailwindConfigFile();
|
||||
// });
|
||||
//
|
||||
// afterAll(() => cleanupProject());
|
||||
//
|
||||
// describe('Libraries', () => {
|
||||
// const createLibComponent = (
|
||||
// lib: string,
|
||||
// buttonBgColor: string = defaultButtonBgColor
|
||||
// ) => {
|
||||
// updateFile(
|
||||
// `libs/${lib}/src/lib/foo.component.ts`,
|
||||
// `import { Component } from '@angular/core';
|
||||
//
|
||||
// @Component({
|
||||
// selector: '${project}-foo',
|
||||
// template: '<button class="custom-btn text-white ${buttonBgColor}">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';
|
||||
// `
|
||||
// );
|
||||
// };
|
||||
//
|
||||
// const assertLibComponentStyles = (
|
||||
// lib: string,
|
||||
// libSpacing: typeof spacing['root']
|
||||
// ) => {
|
||||
// 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 generate a buildable library with tailwind and build correctly', () => {
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:lib ${buildLibWithTailwind.name} --buildable --add-tailwind --no-interactive`
|
||||
// );
|
||||
// updateTailwindConfig(
|
||||
// `libs/${buildLibWithTailwind.name}/tailwind.config.js`,
|
||||
// spacing.projectVariant1
|
||||
// );
|
||||
// createLibComponent(
|
||||
// buildLibWithTailwind.name,
|
||||
// buildLibWithTailwind.buttonBgColor
|
||||
// );
|
||||
//
|
||||
// runCLI(`build ${buildLibWithTailwind.name}`);
|
||||
//
|
||||
// assertLibComponentStyles(
|
||||
// buildLibWithTailwind.name,
|
||||
// spacing.projectVariant1
|
||||
// );
|
||||
// });
|
||||
//
|
||||
// it('should set up tailwind in a previously generated buildable library and build correctly', () => {
|
||||
// const buildLibSetupTailwind = uniq('build-lib-setup-tailwind');
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:lib ${buildLibSetupTailwind} --buildable --no-interactive`
|
||||
// );
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:setup-tailwind ${buildLibSetupTailwind} --no-interactive`
|
||||
// );
|
||||
// updateTailwindConfig(
|
||||
// `libs/${buildLibSetupTailwind}/tailwind.config.js`,
|
||||
// spacing.projectVariant2
|
||||
// );
|
||||
// createLibComponent(buildLibSetupTailwind);
|
||||
//
|
||||
// runCLI(`build ${buildLibSetupTailwind}`);
|
||||
//
|
||||
// assertLibComponentStyles(buildLibSetupTailwind, spacing.projectVariant2);
|
||||
// });
|
||||
//
|
||||
// it('should correctly build a buildable library with a tailwind.config.js file in the project root or workspace root', () => {
|
||||
// const buildLibNoProjectConfig = uniq('build-lib-no-project-config');
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:lib ${buildLibNoProjectConfig} --buildable --no-interactive`
|
||||
// );
|
||||
// createTailwindConfigFile(
|
||||
// `libs/${buildLibNoProjectConfig}/tailwind.config.js`,
|
||||
// spacing.projectVariant3
|
||||
// );
|
||||
// createLibComponent(buildLibNoProjectConfig);
|
||||
//
|
||||
// runCLI(`build ${buildLibNoProjectConfig}`);
|
||||
//
|
||||
// assertLibComponentStyles(
|
||||
// buildLibNoProjectConfig,
|
||||
// spacing.projectVariant3
|
||||
// );
|
||||
//
|
||||
// // remove tailwind.config.js file from the project root to test the one in the workspace root
|
||||
// removeFile(`libs/${buildLibNoProjectConfig}/tailwind.config.js`);
|
||||
//
|
||||
// runCLI(`build ${buildLibNoProjectConfig}`);
|
||||
//
|
||||
// assertLibComponentStyles(buildLibNoProjectConfig, spacing.root);
|
||||
// });
|
||||
//
|
||||
// it('should generate a publishable library with tailwind and build correctly', () => {
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:lib ${pubLibWithTailwind.name} --publishable --add-tailwind --importPath=@${project}/${pubLibWithTailwind.name} --no-interactive`
|
||||
// );
|
||||
// updateTailwindConfig(
|
||||
// `libs/${pubLibWithTailwind.name}/tailwind.config.js`,
|
||||
// spacing.projectVariant1
|
||||
// );
|
||||
// createLibComponent(
|
||||
// pubLibWithTailwind.name,
|
||||
// pubLibWithTailwind.buttonBgColor
|
||||
// );
|
||||
//
|
||||
// runCLI(`build ${pubLibWithTailwind.name}`);
|
||||
//
|
||||
// assertLibComponentStyles(
|
||||
// pubLibWithTailwind.name,
|
||||
// spacing.projectVariant1
|
||||
// );
|
||||
// });
|
||||
//
|
||||
// it('should set up tailwind in a previously generated publishable library and build correctly', () => {
|
||||
// const pubLibSetupTailwind = uniq('pub-lib-setup-tailwind');
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:lib ${pubLibSetupTailwind} --publishable --importPath=@${project}/${pubLibSetupTailwind} --no-interactive`
|
||||
// );
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:setup-tailwind ${pubLibSetupTailwind} --no-interactive`
|
||||
// );
|
||||
// updateTailwindConfig(
|
||||
// `libs/${pubLibSetupTailwind}/tailwind.config.js`,
|
||||
// spacing.projectVariant2
|
||||
// );
|
||||
// createLibComponent(pubLibSetupTailwind);
|
||||
//
|
||||
// runCLI(`build ${pubLibSetupTailwind}`);
|
||||
//
|
||||
// assertLibComponentStyles(pubLibSetupTailwind, spacing.projectVariant2);
|
||||
// });
|
||||
//
|
||||
// it('should correctly build a publishable library with a tailwind.config.js file in the project root or workspace root', () => {
|
||||
// const pubLibNoProjectConfig = uniq('pub-lib-no-project-config');
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:lib ${pubLibNoProjectConfig} --publishable --importPath=@${project}/${pubLibNoProjectConfig} --no-interactive`
|
||||
// );
|
||||
// createTailwindConfigFile(
|
||||
// `libs/${pubLibNoProjectConfig}/tailwind.config.js`,
|
||||
// spacing.projectVariant3
|
||||
// );
|
||||
// createLibComponent(pubLibNoProjectConfig);
|
||||
//
|
||||
// runCLI(`build ${pubLibNoProjectConfig}`);
|
||||
//
|
||||
// assertLibComponentStyles(pubLibNoProjectConfig, spacing.projectVariant3);
|
||||
//
|
||||
// // remove tailwind.config.js file from the project root to test the one in the workspace root
|
||||
// removeFile(`libs/${pubLibNoProjectConfig}/tailwind.config.js`);
|
||||
//
|
||||
// runCLI(`build ${pubLibNoProjectConfig}`);
|
||||
//
|
||||
// assertLibComponentStyles(pubLibNoProjectConfig, spacing.root);
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe('Applications', () => {
|
||||
// const updateAppComponent = (app: string) => {
|
||||
// updateFile(
|
||||
// `apps/${app}/src/app/app.component.html`,
|
||||
// `<button class="custom-btn text-white">Click me!</button>`
|
||||
// );
|
||||
//
|
||||
// updateFile(
|
||||
// `apps/${app}/src/app/app.component.css`,
|
||||
// `.custom-btn {
|
||||
// @apply m-md p-sm;
|
||||
// }`
|
||||
// );
|
||||
// };
|
||||
//
|
||||
// const readAppStylesBundle = (app: string) => {
|
||||
// const stylesBundlePath = listFiles(`dist/apps/${app}`).find((file) =>
|
||||
// file.startsWith('styles.')
|
||||
// );
|
||||
// const stylesBundle = readFile(`dist/apps/${app}/${stylesBundlePath}`);
|
||||
//
|
||||
// return stylesBundle;
|
||||
// };
|
||||
//
|
||||
// const assertAppComponentStyles = (
|
||||
// app: string,
|
||||
// appSpacing: typeof spacing['root']
|
||||
// ) => {
|
||||
// const mainBundlePath = listFiles(`dist/apps/${app}`).find((file) =>
|
||||
// file.startsWith('main.')
|
||||
// );
|
||||
// const mainBundle = readFile(`dist/apps/${app}/${mainBundlePath}`);
|
||||
// let expectedStylesRegex = new RegExp(
|
||||
// `styles:\\[\\"\\.custom\\-btn\\[_ngcontent\\-%COMP%\\]{margin:${appSpacing.md};padding:${appSpacing.sm}}\\"\\]`
|
||||
// );
|
||||
//
|
||||
// expect(mainBundle).toMatch(expectedStylesRegex);
|
||||
// };
|
||||
//
|
||||
// it('should build correctly and only output the tailwind utilities used', () => {
|
||||
// const appWithTailwind = uniq('app-with-tailwind');
|
||||
// runCLI(
|
||||
// `generate @nrwl/angular:app ${appWithTailwind} --add-tailwind --no-interactive`
|
||||
// );
|
||||
// updateTailwindConfig(
|
||||
// `apps/${appWithTailwind}/tailwind.config.js`,
|
||||
// spacing.projectVariant1
|
||||
// );
|
||||
// updateFile(
|
||||
// `apps/${appWithTailwind}/src/app/app.module.ts`,
|
||||
// `import { NgModule } from '@angular/core';
|
||||
// import { BrowserModule } from '@angular/platform-browser';
|
||||
// import { LibModule as LibModule1 } from '@${project}/${buildLibWithTailwind.name}';
|
||||
// import { LibModule as LibModule2 } from '@${project}/${pubLibWithTailwind.name}';
|
||||
//
|
||||
// import { AppComponent } from './app.component';
|
||||
//
|
||||
// @NgModule({
|
||||
// declarations: [AppComponent],
|
||||
// imports: [BrowserModule, LibModule1, LibModule2],
|
||||
// providers: [],
|
||||
// bootstrap: [AppComponent],
|
||||
// })
|
||||
// export class AppModule {}
|
||||
// `
|
||||
// );
|
||||
// updateAppComponent(appWithTailwind);
|
||||
//
|
||||
// runCLI(`build ${appWithTailwind}`);
|
||||
//
|
||||
// assertAppComponentStyles(appWithTailwind, spacing.projectVariant1);
|
||||
// let stylesBundle = readAppStylesBundle(appWithTailwind);
|
||||
// expect(stylesBundle).toContain('.text-white');
|
||||
// expect(stylesBundle).not.toContain('.text-black');
|
||||
// expect(stylesBundle).toContain(`.${buildLibWithTailwind.buttonBgColor}`);
|
||||
// expect(stylesBundle).toContain(`.${pubLibWithTailwind.buttonBgColor}`);
|
||||
// expect(stylesBundle).not.toContain(`.${defaultButtonBgColor}`);
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
@ -58,7 +58,7 @@ export const nxCompileNgcTransformFactory = (
|
||||
declaration: true,
|
||||
target: ts.ScriptTarget.ES2020,
|
||||
},
|
||||
entryPoint.cache.stylesheetProcessor,
|
||||
entryPoint.cache.stylesheetProcessor as any,
|
||||
null,
|
||||
options.watch
|
||||
);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
*
|
||||
* Changes made:
|
||||
* - Made sure ngccProcessor is optional.
|
||||
* - Use custom cacheCompilerHost instead of the one provided by ng-packagr.
|
||||
*/
|
||||
|
||||
import type {
|
||||
@ -17,15 +18,13 @@ import {
|
||||
PackageNode,
|
||||
} from 'ng-packagr/lib/ng-package/nodes';
|
||||
import { NgccProcessor } from 'ng-packagr/lib/ngc/ngcc-processor';
|
||||
import { StylesheetProcessor } from 'ng-packagr/lib/styles/stylesheet-processor';
|
||||
import {
|
||||
augmentProgramWithVersioning,
|
||||
cacheCompilerHost,
|
||||
} from 'ng-packagr/lib/ts/cache-compiler-host';
|
||||
import { augmentProgramWithVersioning } from 'ng-packagr/lib/ts/cache-compiler-host';
|
||||
import { ngccTransformCompilerHost } from 'ng-packagr/lib/ts/ngcc-transform-compiler-host';
|
||||
import * as log from 'ng-packagr/lib/utils/log';
|
||||
import { ngCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
|
||||
import * as ts from 'typescript';
|
||||
import { StylesheetProcessor } from '../styles/stylesheet-processor';
|
||||
import { cacheCompilerHost } from '../ts/cache-compiler-host';
|
||||
|
||||
export async function compileSourceFiles(
|
||||
graph: BuildGraph,
|
||||
|
||||
@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Changed filePath passed to the StylesheetProcessor.parse when is a .ts file and inlineStyleLanguage is set.
|
||||
*/
|
||||
|
||||
import type { CompilerHost, CompilerOptions } from '@angular/compiler-cli';
|
||||
import { createHash } from 'crypto';
|
||||
import { FileCache } from 'ng-packagr/lib/file-system/file-cache';
|
||||
import { BuildGraph } from 'ng-packagr/lib/graph/build-graph';
|
||||
import { Node } from 'ng-packagr/lib/graph/node';
|
||||
import { EntryPointNode, fileUrl } from 'ng-packagr/lib/ng-package/nodes';
|
||||
import { ensureUnixPath } from 'ng-packagr/lib/utils/path';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import {
|
||||
InlineStyleLanguage,
|
||||
StylesheetProcessor,
|
||||
} from '../styles/stylesheet-processor';
|
||||
|
||||
export function cacheCompilerHost(
|
||||
graph: BuildGraph,
|
||||
entryPoint: EntryPointNode,
|
||||
compilerOptions: CompilerOptions,
|
||||
moduleResolutionCache: ts.ModuleResolutionCache,
|
||||
stylesheetProcessor?: StylesheetProcessor,
|
||||
inlineStyleLanguage?: InlineStyleLanguage,
|
||||
sourcesFileCache: FileCache = entryPoint.cache.sourcesFileCache
|
||||
): CompilerHost {
|
||||
const compilerHost = ts.createIncrementalCompilerHost(compilerOptions);
|
||||
|
||||
const getNode = (fileName: string) => {
|
||||
const nodeUri = fileUrl(ensureUnixPath(fileName));
|
||||
let node = graph.get(nodeUri);
|
||||
|
||||
if (!node) {
|
||||
node = new Node(nodeUri);
|
||||
graph.put(node);
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
const addDependee = (fileName: string) => {
|
||||
const node = getNode(fileName);
|
||||
entryPoint.dependsOn(node);
|
||||
};
|
||||
|
||||
return {
|
||||
...compilerHost,
|
||||
|
||||
// ts specific
|
||||
fileExists: (fileName: string) => {
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (cache.exists === undefined) {
|
||||
cache.exists = compilerHost.fileExists.call(this, fileName);
|
||||
}
|
||||
|
||||
return cache.exists;
|
||||
},
|
||||
|
||||
getSourceFile: (fileName: string, languageVersion: ts.ScriptTarget) => {
|
||||
addDependee(fileName);
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (!cache.sourceFile) {
|
||||
cache.sourceFile = compilerHost.getSourceFile.call(
|
||||
this,
|
||||
fileName,
|
||||
languageVersion
|
||||
);
|
||||
}
|
||||
|
||||
return cache.sourceFile;
|
||||
},
|
||||
|
||||
writeFile: (
|
||||
fileName: string,
|
||||
data: string,
|
||||
writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void,
|
||||
sourceFiles?: ReadonlyArray<ts.SourceFile>
|
||||
) => {
|
||||
if (fileName.endsWith('.d.ts')) {
|
||||
sourceFiles.forEach((source) => {
|
||||
const cache = sourcesFileCache.getOrCreate(source.fileName);
|
||||
if (!cache.declarationFileName) {
|
||||
cache.declarationFileName = ensureUnixPath(fileName);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fileName = fileName.replace(/\.js(\.map)?$/, '.mjs$1');
|
||||
const outputCache = entryPoint.cache.outputCache;
|
||||
|
||||
outputCache.set(fileName, {
|
||||
content: data,
|
||||
version: createHash('sha256').update(data).digest('hex'),
|
||||
});
|
||||
}
|
||||
|
||||
compilerHost.writeFile.call(
|
||||
this,
|
||||
fileName,
|
||||
data,
|
||||
writeByteOrderMark,
|
||||
onError,
|
||||
sourceFiles
|
||||
);
|
||||
},
|
||||
|
||||
readFile: (fileName: string) => {
|
||||
addDependee(fileName);
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (cache.content === undefined) {
|
||||
cache.content = compilerHost.readFile.call(this, fileName);
|
||||
}
|
||||
|
||||
return cache.content;
|
||||
},
|
||||
|
||||
resolveModuleNames: (moduleNames: string[], containingFile: string) => {
|
||||
return moduleNames.map((moduleName) => {
|
||||
const { resolvedModule } = ts.resolveModuleName(
|
||||
moduleName,
|
||||
ensureUnixPath(containingFile),
|
||||
compilerOptions,
|
||||
compilerHost,
|
||||
moduleResolutionCache
|
||||
);
|
||||
|
||||
return resolvedModule;
|
||||
});
|
||||
},
|
||||
|
||||
resourceNameToFileName: (
|
||||
resourceName: string,
|
||||
containingFilePath: string
|
||||
) => {
|
||||
const resourcePath = path.resolve(
|
||||
path.dirname(containingFilePath),
|
||||
resourceName
|
||||
);
|
||||
const containingNode = getNode(containingFilePath);
|
||||
const resourceNode = getNode(resourcePath);
|
||||
containingNode.dependsOn(resourceNode);
|
||||
|
||||
return resourcePath;
|
||||
},
|
||||
|
||||
readResource: async (fileName: string) => {
|
||||
addDependee(fileName);
|
||||
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (cache.content === undefined) {
|
||||
if (/(?:html?|svg)$/.test(path.extname(fileName))) {
|
||||
// template
|
||||
cache.content = compilerHost.readFile.call(this, fileName);
|
||||
} else {
|
||||
// stylesheet
|
||||
cache.content = await stylesheetProcessor.process({
|
||||
filePath: fileName,
|
||||
content: compilerHost.readFile.call(this, fileName),
|
||||
});
|
||||
}
|
||||
|
||||
if (cache.content === undefined) {
|
||||
throw new Error(`Cannot read file ${fileName}.`);
|
||||
}
|
||||
|
||||
cache.exists = true;
|
||||
}
|
||||
|
||||
return cache.content;
|
||||
},
|
||||
transformResource: async (data, context) => {
|
||||
if (context.resourceFile || context.type !== 'style') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inlineStyleLanguage) {
|
||||
const key = createHash('sha1').update(data).digest('hex');
|
||||
const fileName = `${context.containingFile}-${key}.${inlineStyleLanguage}`;
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (cache.content === undefined) {
|
||||
cache.content = await stylesheetProcessor.process({
|
||||
filePath: context.containingFile, // @leosvelperez: changed from fileName
|
||||
content: data,
|
||||
});
|
||||
|
||||
const virtualFileNode = getNode(fileName);
|
||||
const containingFileNode = getNode(context.containingFile);
|
||||
virtualFileNode.dependsOn(containingFileNode);
|
||||
}
|
||||
|
||||
cache.exists = true;
|
||||
|
||||
return { content: cache.content };
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function augmentProgramWithVersioning(program: ts.Program): void {
|
||||
const baseGetSourceFiles = program.getSourceFiles;
|
||||
program.getSourceFiles = function (...parameters) {
|
||||
const files: readonly (ts.SourceFile & { version?: string })[] =
|
||||
baseGetSourceFiles(...parameters);
|
||||
|
||||
for (const file of files) {
|
||||
if (file.version === undefined) {
|
||||
file.version = createHash('sha256').update(file.text).digest('hex');
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
}
|
||||
@ -14,13 +14,13 @@ import {
|
||||
isEntryPoint,
|
||||
isEntryPointInProgress,
|
||||
} from 'ng-packagr/lib/ng-package/nodes';
|
||||
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 { ngccCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
|
||||
import * as ora from 'ora';
|
||||
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';
|
||||
|
||||
@ -85,7 +85,7 @@ export const compileNgcTransformFactory = (
|
||||
declaration: true,
|
||||
target: ts.ScriptTarget.ES2020,
|
||||
},
|
||||
entryPoint.cache.stylesheetProcessor,
|
||||
entryPoint.cache.stylesheetProcessor as any,
|
||||
ngccProcessor,
|
||||
options.watch
|
||||
);
|
||||
|
||||
@ -0,0 +1,221 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Use custom cacheCompilerHost instead of the one provided by ng-packagr.
|
||||
*/
|
||||
|
||||
import type {
|
||||
CompilerOptions,
|
||||
ParsedConfiguration,
|
||||
} from '@angular/compiler-cli';
|
||||
import { BuildGraph } from 'ng-packagr/lib/graph/build-graph';
|
||||
import {
|
||||
EntryPointNode,
|
||||
isEntryPointInProgress,
|
||||
isPackage,
|
||||
PackageNode,
|
||||
} from 'ng-packagr/lib/ng-package/nodes';
|
||||
import { NgccProcessor } from 'ng-packagr/lib/ngc/ngcc-processor';
|
||||
import { ngccTransformCompilerHost } from 'ng-packagr/lib/ts/ngcc-transform-compiler-host';
|
||||
import * as log from 'ng-packagr/lib/utils/log';
|
||||
import { ngCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
|
||||
import * as ts from 'typescript';
|
||||
import { StylesheetProcessor } from '../styles/stylesheet-processor';
|
||||
import {
|
||||
augmentProgramWithVersioning,
|
||||
cacheCompilerHost,
|
||||
} from '../ts/cache-compiler-host';
|
||||
|
||||
export async function compileSourceFiles(
|
||||
graph: BuildGraph,
|
||||
tsConfig: ParsedConfiguration,
|
||||
moduleResolutionCache: ts.ModuleResolutionCache,
|
||||
extraOptions?: Partial<CompilerOptions>,
|
||||
stylesheetProcessor?: StylesheetProcessor,
|
||||
ngccProcessor?: NgccProcessor,
|
||||
watch?: boolean
|
||||
) {
|
||||
const { NgtscProgram, formatDiagnostics } = await ngCompilerCli();
|
||||
|
||||
const tsConfigOptions: CompilerOptions = {
|
||||
...tsConfig.options,
|
||||
...extraOptions,
|
||||
};
|
||||
const entryPoint: EntryPointNode = graph.find(isEntryPointInProgress());
|
||||
const ngPackageNode: PackageNode = graph.find(isPackage);
|
||||
const inlineStyleLanguage = ngPackageNode.data.inlineStyleLanguage;
|
||||
|
||||
const tsCompilerHost = ngccTransformCompilerHost(
|
||||
cacheCompilerHost(
|
||||
graph,
|
||||
entryPoint,
|
||||
tsConfigOptions,
|
||||
moduleResolutionCache,
|
||||
stylesheetProcessor,
|
||||
inlineStyleLanguage
|
||||
),
|
||||
tsConfigOptions,
|
||||
ngccProcessor,
|
||||
moduleResolutionCache
|
||||
);
|
||||
|
||||
const cache = entryPoint.cache;
|
||||
const sourceFileCache = cache.sourcesFileCache;
|
||||
|
||||
// Create the Angular specific program that contains the Angular compiler
|
||||
const angularProgram = new NgtscProgram(
|
||||
tsConfig.rootNames,
|
||||
tsConfigOptions,
|
||||
tsCompilerHost,
|
||||
cache.oldNgtscProgram
|
||||
);
|
||||
|
||||
const angularCompiler = angularProgram.compiler;
|
||||
const { ignoreForDiagnostics, ignoreForEmit } = angularCompiler;
|
||||
|
||||
// SourceFile versions are required for builder programs.
|
||||
// The wrapped host inside NgtscProgram adds additional files that will not have versions.
|
||||
const typeScriptProgram = angularProgram.getTsProgram();
|
||||
augmentProgramWithVersioning(typeScriptProgram);
|
||||
|
||||
let builder: ts.BuilderProgram | ts.EmitAndSemanticDiagnosticsBuilderProgram;
|
||||
if (watch) {
|
||||
builder = cache.oldBuilder =
|
||||
ts.createEmitAndSemanticDiagnosticsBuilderProgram(
|
||||
typeScriptProgram,
|
||||
tsCompilerHost,
|
||||
cache.oldBuilder
|
||||
);
|
||||
cache.oldNgtscProgram = angularProgram;
|
||||
} else {
|
||||
// When not in watch mode, the startup cost of the incremental analysis can be avoided by
|
||||
// using an abstract builder that only wraps a TypeScript program.
|
||||
builder = ts.createAbstractBuilder(typeScriptProgram, tsCompilerHost);
|
||||
}
|
||||
|
||||
// Update semantic diagnostics cache
|
||||
const affectedFiles = new Set<ts.SourceFile>();
|
||||
|
||||
// Analyze affected files when in watch mode for incremental type checking
|
||||
if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(
|
||||
undefined,
|
||||
(sourceFile) => {
|
||||
// If the affected file is a TTC shim, add the shim's original source file.
|
||||
// This ensures that changes that affect TTC are typechecked even when the changes
|
||||
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
|
||||
// For example, changing @Input property types of a directive used in another component's
|
||||
// template.
|
||||
if (
|
||||
ignoreForDiagnostics.has(sourceFile) &&
|
||||
sourceFile.fileName.endsWith('.ngtypecheck.ts')
|
||||
) {
|
||||
// This file name conversion relies on internal compiler logic and should be converted
|
||||
// to an official method when available. 15 is length of `.ngtypecheck.ts`
|
||||
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
|
||||
const originalSourceFile = builder.getSourceFile(originalFilename);
|
||||
if (originalSourceFile) {
|
||||
affectedFiles.add(originalSourceFile);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
break;
|
||||
}
|
||||
|
||||
affectedFiles.add(result.affected as ts.SourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect program level diagnostics
|
||||
const allDiagnostics: ts.Diagnostic[] = [
|
||||
...angularCompiler.getOptionDiagnostics(),
|
||||
...builder.getOptionsDiagnostics(),
|
||||
...builder.getGlobalDiagnostics(),
|
||||
];
|
||||
|
||||
// Required to support asynchronous resource loading
|
||||
// Must be done before creating transformers or getting template diagnostics
|
||||
await angularCompiler.analyzeAsync();
|
||||
|
||||
// Collect source file specific diagnostics
|
||||
for (const sourceFile of builder.getSourceFiles()) {
|
||||
if (!ignoreForDiagnostics.has(sourceFile)) {
|
||||
allDiagnostics.push(
|
||||
...builder.getSyntacticDiagnostics(sourceFile),
|
||||
...builder.getSemanticDiagnostics(sourceFile)
|
||||
);
|
||||
}
|
||||
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect sources that are required to be emitted
|
||||
if (
|
||||
!ignoreForEmit.has(sourceFile) &&
|
||||
!angularCompiler.incrementalDriver.safeToSkipEmit(sourceFile)
|
||||
) {
|
||||
// If required to emit, diagnostics may have also changed
|
||||
if (!ignoreForDiagnostics.has(sourceFile)) {
|
||||
affectedFiles.add(sourceFile);
|
||||
}
|
||||
} else if (
|
||||
sourceFileCache &&
|
||||
!affectedFiles.has(sourceFile) &&
|
||||
!ignoreForDiagnostics.has(sourceFile)
|
||||
) {
|
||||
// Use cached Angular diagnostics for unchanged and unaffected files
|
||||
const angularDiagnostics =
|
||||
sourceFileCache.getAngularDiagnostics(sourceFile);
|
||||
if (angularDiagnostics?.length) {
|
||||
allDiagnostics.push(...angularDiagnostics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect new Angular diagnostics for files affected by changes
|
||||
for (const affectedFile of affectedFiles) {
|
||||
const angularDiagnostics = angularCompiler.getDiagnosticsForFile(
|
||||
affectedFile,
|
||||
/** OptimizeFor.WholeProgram */ 1
|
||||
);
|
||||
|
||||
allDiagnostics.push(...angularDiagnostics);
|
||||
sourceFileCache.updateAngularDiagnostics(affectedFile, angularDiagnostics);
|
||||
}
|
||||
|
||||
const otherDiagnostics = [];
|
||||
const errorDiagnostics = [];
|
||||
for (const diagnostic of allDiagnostics) {
|
||||
if (diagnostic.category === ts.DiagnosticCategory.Error) {
|
||||
errorDiagnostics.push(diagnostic);
|
||||
} else {
|
||||
otherDiagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
if (otherDiagnostics.length) {
|
||||
log.msg(formatDiagnostics(errorDiagnostics));
|
||||
}
|
||||
|
||||
if (errorDiagnostics.length) {
|
||||
throw new Error(formatDiagnostics(errorDiagnostics));
|
||||
}
|
||||
|
||||
const transformers = angularCompiler.prepareEmit().transformers;
|
||||
for (const sourceFile of builder.getSourceFiles()) {
|
||||
if (!ignoreForEmit.has(sourceFile)) {
|
||||
builder.emit(sourceFile, undefined, undefined, undefined, transformers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Adapted from the original ng-packagr source.
|
||||
*
|
||||
* Changes made:
|
||||
* - Changed filePath passed to the StylesheetProcessor.parse when is a .ts file and inlineStyleLanguage is set.
|
||||
*/
|
||||
|
||||
import type { CompilerHost, CompilerOptions } from '@angular/compiler-cli';
|
||||
import { createHash } from 'crypto';
|
||||
import { FileCache } from 'ng-packagr/lib/file-system/file-cache';
|
||||
import { BuildGraph } from 'ng-packagr/lib/graph/build-graph';
|
||||
import { Node } from 'ng-packagr/lib/graph/node';
|
||||
import { EntryPointNode, fileUrl } from 'ng-packagr/lib/ng-package/nodes';
|
||||
import { ensureUnixPath } from 'ng-packagr/lib/utils/path';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import {
|
||||
InlineStyleLanguage,
|
||||
StylesheetProcessor,
|
||||
} from '../styles/stylesheet-processor';
|
||||
|
||||
export function cacheCompilerHost(
|
||||
graph: BuildGraph,
|
||||
entryPoint: EntryPointNode,
|
||||
compilerOptions: CompilerOptions,
|
||||
moduleResolutionCache: ts.ModuleResolutionCache,
|
||||
stylesheetProcessor?: StylesheetProcessor,
|
||||
inlineStyleLanguage?: InlineStyleLanguage,
|
||||
sourcesFileCache: FileCache = entryPoint.cache.sourcesFileCache
|
||||
): CompilerHost {
|
||||
const compilerHost = ts.createIncrementalCompilerHost(compilerOptions);
|
||||
|
||||
const getNode = (fileName: string) => {
|
||||
const nodeUri = fileUrl(ensureUnixPath(fileName));
|
||||
let node = graph.get(nodeUri);
|
||||
|
||||
if (!node) {
|
||||
node = new Node(nodeUri);
|
||||
graph.put(node);
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
const addDependee = (fileName: string) => {
|
||||
const node = getNode(fileName);
|
||||
entryPoint.dependsOn(node);
|
||||
};
|
||||
|
||||
return {
|
||||
...compilerHost,
|
||||
|
||||
// ts specific
|
||||
fileExists: (fileName: string) => {
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (cache.exists === undefined) {
|
||||
cache.exists = compilerHost.fileExists.call(this, fileName);
|
||||
}
|
||||
|
||||
return cache.exists;
|
||||
},
|
||||
|
||||
getSourceFile: (fileName: string, languageVersion: ts.ScriptTarget) => {
|
||||
addDependee(fileName);
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (!cache.sourceFile) {
|
||||
cache.sourceFile = compilerHost.getSourceFile.call(
|
||||
this,
|
||||
fileName,
|
||||
languageVersion
|
||||
);
|
||||
}
|
||||
|
||||
return cache.sourceFile;
|
||||
},
|
||||
|
||||
writeFile: (
|
||||
fileName: string,
|
||||
data: string,
|
||||
writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void,
|
||||
sourceFiles?: ReadonlyArray<ts.SourceFile>
|
||||
) => {
|
||||
if (fileName.endsWith('.d.ts')) {
|
||||
sourceFiles.forEach((source) => {
|
||||
const cache = sourcesFileCache.getOrCreate(source.fileName);
|
||||
if (!cache.declarationFileName) {
|
||||
cache.declarationFileName = ensureUnixPath(fileName);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fileName = fileName.replace(/\.js(\.map)?$/, '.mjs$1');
|
||||
const outputCache = entryPoint.cache.outputCache;
|
||||
|
||||
outputCache.set(fileName, {
|
||||
content: data,
|
||||
version: createHash('sha256').update(data).digest('hex'),
|
||||
});
|
||||
}
|
||||
|
||||
compilerHost.writeFile.call(
|
||||
this,
|
||||
fileName,
|
||||
data,
|
||||
writeByteOrderMark,
|
||||
onError,
|
||||
sourceFiles
|
||||
);
|
||||
},
|
||||
|
||||
readFile: (fileName: string) => {
|
||||
addDependee(fileName);
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (cache.content === undefined) {
|
||||
cache.content = compilerHost.readFile.call(this, fileName);
|
||||
}
|
||||
|
||||
return cache.content;
|
||||
},
|
||||
|
||||
resolveModuleNames: (moduleNames: string[], containingFile: string) => {
|
||||
return moduleNames.map((moduleName) => {
|
||||
const { resolvedModule } = ts.resolveModuleName(
|
||||
moduleName,
|
||||
ensureUnixPath(containingFile),
|
||||
compilerOptions,
|
||||
compilerHost,
|
||||
moduleResolutionCache
|
||||
);
|
||||
|
||||
return resolvedModule;
|
||||
});
|
||||
},
|
||||
|
||||
resourceNameToFileName: (
|
||||
resourceName: string,
|
||||
containingFilePath: string
|
||||
) => {
|
||||
const resourcePath = path.resolve(
|
||||
path.dirname(containingFilePath),
|
||||
resourceName
|
||||
);
|
||||
const containingNode = getNode(containingFilePath);
|
||||
const resourceNode = getNode(resourcePath);
|
||||
containingNode.dependsOn(resourceNode);
|
||||
|
||||
return resourcePath;
|
||||
},
|
||||
|
||||
readResource: async (fileName: string) => {
|
||||
addDependee(fileName);
|
||||
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (cache.content === undefined) {
|
||||
if (/(?:html?|svg)$/.test(path.extname(fileName))) {
|
||||
// template
|
||||
cache.content = compilerHost.readFile.call(this, fileName);
|
||||
} else {
|
||||
// stylesheet
|
||||
cache.content = await stylesheetProcessor.process({
|
||||
filePath: fileName,
|
||||
content: compilerHost.readFile.call(this, fileName),
|
||||
});
|
||||
}
|
||||
|
||||
if (cache.content === undefined) {
|
||||
throw new Error(`Cannot read file ${fileName}.`);
|
||||
}
|
||||
|
||||
cache.exists = true;
|
||||
}
|
||||
|
||||
return cache.content;
|
||||
},
|
||||
transformResource: async (data, context) => {
|
||||
if (context.resourceFile || context.type !== 'style') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inlineStyleLanguage) {
|
||||
const key = createHash('sha1').update(data).digest('hex');
|
||||
const fileName = `${context.containingFile}-${key}.${inlineStyleLanguage}`;
|
||||
const cache = sourcesFileCache.getOrCreate(fileName);
|
||||
if (cache.content === undefined) {
|
||||
cache.content = await stylesheetProcessor.process({
|
||||
filePath: context.containingFile, // @leosvelperez: changed from fileName
|
||||
content: data,
|
||||
});
|
||||
|
||||
const virtualFileNode = getNode(fileName);
|
||||
const containingFileNode = getNode(context.containingFile);
|
||||
virtualFileNode.dependsOn(containingFileNode);
|
||||
}
|
||||
|
||||
cache.exists = true;
|
||||
|
||||
return { content: cache.content };
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function augmentProgramWithVersioning(program: ts.Program): void {
|
||||
const baseGetSourceFiles = program.getSourceFiles;
|
||||
program.getSourceFiles = function (...parameters) {
|
||||
const files: readonly (ts.SourceFile & { version?: string })[] =
|
||||
baseGetSourceFiles(...parameters);
|
||||
|
||||
for (const file of files) {
|
||||
if (file.version === undefined) {
|
||||
file.version = createHash('sha256').update(file.text).digest('hex');
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user