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';
|
} from '@nrwl/e2e/utils';
|
||||||
|
|
||||||
describe('Tailwind support', () => {
|
describe('Tailwind support', () => {
|
||||||
it('tests are disabled', () => {
|
let project: string;
|
||||||
expect(1).toEqual(1);
|
|
||||||
|
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,
|
declaration: true,
|
||||||
target: ts.ScriptTarget.ES2020,
|
target: ts.ScriptTarget.ES2020,
|
||||||
},
|
},
|
||||||
entryPoint.cache.stylesheetProcessor,
|
entryPoint.cache.stylesheetProcessor as any,
|
||||||
null,
|
null,
|
||||||
options.watch
|
options.watch
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Changes made:
|
* Changes made:
|
||||||
* - Made sure ngccProcessor is optional.
|
* - Made sure ngccProcessor is optional.
|
||||||
|
* - Use custom cacheCompilerHost instead of the one provided by ng-packagr.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -17,15 +18,13 @@ import {
|
|||||||
PackageNode,
|
PackageNode,
|
||||||
} from 'ng-packagr/lib/ng-package/nodes';
|
} from 'ng-packagr/lib/ng-package/nodes';
|
||||||
import { NgccProcessor } from 'ng-packagr/lib/ngc/ngcc-processor';
|
import { NgccProcessor } from 'ng-packagr/lib/ngc/ngcc-processor';
|
||||||
import { StylesheetProcessor } from 'ng-packagr/lib/styles/stylesheet-processor';
|
import { augmentProgramWithVersioning } from 'ng-packagr/lib/ts/cache-compiler-host';
|
||||||
import {
|
|
||||||
augmentProgramWithVersioning,
|
|
||||||
cacheCompilerHost,
|
|
||||||
} from 'ng-packagr/lib/ts/cache-compiler-host';
|
|
||||||
import { ngccTransformCompilerHost } from 'ng-packagr/lib/ts/ngcc-transform-compiler-host';
|
import { ngccTransformCompilerHost } from 'ng-packagr/lib/ts/ngcc-transform-compiler-host';
|
||||||
import * as log from 'ng-packagr/lib/utils/log';
|
import * as log from 'ng-packagr/lib/utils/log';
|
||||||
import { ngCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
|
import { ngCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
import { StylesheetProcessor } from '../styles/stylesheet-processor';
|
||||||
|
import { cacheCompilerHost } from '../ts/cache-compiler-host';
|
||||||
|
|
||||||
export async function compileSourceFiles(
|
export async function compileSourceFiles(
|
||||||
graph: BuildGraph,
|
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,
|
isEntryPoint,
|
||||||
isEntryPointInProgress,
|
isEntryPointInProgress,
|
||||||
} from 'ng-packagr/lib/ng-package/nodes';
|
} 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 { NgccProcessor } from 'ng-packagr/lib/ngc/ngcc-processor';
|
||||||
import { setDependenciesTsConfigPaths } from 'ng-packagr/lib/ts/tsconfig';
|
import { setDependenciesTsConfigPaths } from 'ng-packagr/lib/ts/tsconfig';
|
||||||
import { ngccCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
|
import { ngccCompilerCli } from 'ng-packagr/lib/utils/ng-compiler-cli';
|
||||||
import * as ora from 'ora';
|
import * as ora from 'ora';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
import { compileSourceFiles } from '../../ngc/compile-source-files';
|
||||||
import { StylesheetProcessor as StylesheetProcessorClass } from '../../styles/stylesheet-processor';
|
import { StylesheetProcessor as StylesheetProcessorClass } from '../../styles/stylesheet-processor';
|
||||||
import { NgPackagrOptions } from '../options.di';
|
import { NgPackagrOptions } from '../options.di';
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ export const compileNgcTransformFactory = (
|
|||||||
declaration: true,
|
declaration: true,
|
||||||
target: ts.ScriptTarget.ES2020,
|
target: ts.ScriptTarget.ES2020,
|
||||||
},
|
},
|
||||||
entryPoint.cache.stylesheetProcessor,
|
entryPoint.cache.stylesheetProcessor as any,
|
||||||
ngccProcessor,
|
ngccProcessor,
|
||||||
options.watch
|
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