Skip to content

Commit

Permalink
feat(build): support @use or @import in component styles
Browse files Browse the repository at this point in the history
  • Loading branch information
Gary Wu committed Oct 9, 2024
1 parent 42bf5d7 commit 46383dc
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 6 deletions.
60 changes: 57 additions & 3 deletions packages/build/src/lib/plugins/angular.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Compiler, RspackPluginInstance } from '@rspack/core';
import { Compiler, RspackPluginInstance, RuleSetRule } from '@rspack/core';
import { JavaScriptTransformer } from '@angular/build/src/tools/esbuild/javascript-transformer';
import { FileReferenceTracker } from '@angular/build/src/tools/esbuild/angular/file-reference-tracker';
// import { ComponentStylesheetBundler } from '@angular/build/src/tools/esbuild/angular/component-stylesheets';
import { ParallelCompilation } from '@angular/build/src/tools/angular/compilation/parallel-compilation';
import { type AngularHostOptions } from '@angular/build/src/tools/angular/angular-host';
import { maxWorkers } from '../utils/utils';
import { compile as sassCompile } from 'sass';
import { Options, compile as sassCompile } from 'sass';
import { normalize } from 'path';
import {
NG_RSPACK_SYMBOL_NAME,
Expand Down Expand Up @@ -37,6 +37,60 @@ export class AngularRspackPlugin implements RspackPluginInstance {
}

apply(compiler: Compiler) {
const modules = compiler.options.resolve.modules || [];
const rules = (compiler.options.module?.rules || []) as RuleSetRule[];

/*
resolve: {
modules: ['node_modules'],
},
...
module: {
rules: [
{
test: /\.?(sa|sc|c)ss$/,
use: [
{
...
options: {
...
sassOptions: {
...
}
},
},
],
type: 'css/auto',
},
],
}
*/
const sassOptions = rules
.filter((rule) => rule?.test instanceof RegExp && rule?.test.test('.sass'))
.map((sassRule) => {
if (Array.isArray(sassRule.use)) {
const useWithSassOptions = sassRule.use.find((use) =>
typeof use !== 'string'
&& typeof use.options === 'object'
&& typeof use.options['sassOptions'] === 'object'
) as unknown as { options: { sassOptions: { loadPaths?: string[] } } } | null;

if (useWithSassOptions) {
return {
...useWithSassOptions.options.sassOptions,
loadPaths: [
...modules,
...(useWithSassOptions.options.sassOptions.loadPaths || []),
],
}
}
}
return null;
})
.filter((sassOptions) => sassOptions)[0] as Options<'sync'>;

compiler.hooks.beforeCompile.tapAsync(
'AngularRspackPlugin',
async (params, callback) => {
Expand All @@ -56,7 +110,7 @@ export class AngularRspackPlugin implements RspackPluginInstance {
return '';
}
try {
const result = sassCompile(stylesheetFile);
const result = sassCompile(stylesheetFile, sassOptions);
return result.css;
} catch (e) {
console.error(
Expand Down
1 change: 1 addition & 0 deletions packages/build/src/lib/plugins/ng-rspack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface NgRspackPluginOptions {
index: string;
tsConfig: string;
styles?: string[];
stylePreprocessorOptions?: { includePaths?: string[] }
scripts?: string[];
polyfills?: string[];
assets?: string[];
Expand Down
13 changes: 13 additions & 0 deletions packages/build/src/lib/utils/create-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NgRspackPlugin, NgRspackPluginOptions } from '../plugins/ng-rspack';

import {
Configuration,
DevServer,
Expand All @@ -9,6 +10,15 @@ import { join, resolve } from 'path';
export function createConfig(options: NgRspackPluginOptions): Configuration {
const isProduction = process.env['NODE_ENV'] === 'production';
const isDevServer = process.env['WEBPACK_SERVE'] && !isProduction;

const includePaths: string[] = [];
if (options.stylePreprocessorOptions?.includePaths?.length) {
options.stylePreprocessorOptions.includePaths.forEach(
(includePath) =>
includePaths.push(join(options.root, includePath))
);
}

return {
context: options.root,
target: 'web',
Expand Down Expand Up @@ -127,6 +137,9 @@ export function createConfig(options: NgRspackPluginOptions): Configuration {
options: {
api: 'modern-compiler',
implementation: require.resolve('sass-embedded'),
sassOptions: {
loadPaths: includePaths,
},
},
},
],
Expand Down
3 changes: 3 additions & 0 deletions packages/nx-plugin/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ export default {
moduleFileExtensions: ['ts', 'js', 'html'],
testEnvironment: '',
coverageDirectory: '../../coverage/packages/nx-plugin',
moduleNameMapper: {
'@ng-rspack/(.*)': 'packages/$1',
}
};
1 change: 1 addition & 0 deletions packages/nx-plugin/src/executors/build/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface BuildExecutorSchema {
tsConfig: string;
mode?: 'production' | 'development' | 'none';
styles?: string[];
stylePreprocessorOptions?: { includePaths?: string[] }
scripts?: string[];
polyfills?: string[];
assets?: string[];
Expand Down
13 changes: 13 additions & 0 deletions packages/nx-plugin/src/executors/build/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@
"description": "Paths to global stylesheets to be included.",
"default": []
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors.",
"type": "object",
"properties": {
"includePaths": {
"description": "Paths to include. Paths will be resolved to workspace root.",
"type": "array",
"items": { "type": "string" },
"default": []
}
},
"additionalProperties": false
},
"scripts": {
"type": "array",
"items": {
Expand Down
100 changes: 100 additions & 0 deletions packages/nx-plugin/src/utils/create-rspack-config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { ExecutorContext } from '@nx/devkit';
import { createRspackConfig } from './create-rspack-config';
import { BuildExecutorSchema } from '../executors/build/schema';
import { Compiler, RspackOptionsNormalized, RuleSetRule } from '@rspack/core';
import { NgRspackPlugin } from 'packages/build/src/lib/plugins/ng-rspack';

describe('Create rspack config', () => {
let executorContext: ExecutorContext = null;
beforeEach(() => {
executorContext = {
root: '/<workspace-root>',
cwd: '/<workspace-root>',
isVerbose: false,
projectName: 'the-project',
projectGraph: {
nodes: {
'the-project': {
type: 'app',
name: 'the-project',
data: {
root: 'apps/the-project',
name: 'the-project',
}
}
},
dependencies: {},
}
};
});

it('resolve paths relative to project root', async () => {
const options: BuildExecutorSchema = {
outputPath: 'dist',
main: 'src/main.ts',
index: 'src/index.html',
tsConfig: 'ts.config.json',
stylePreprocessorOptions: {
includePaths: [
'src/styles',
'../../packages/common/styles',
],
},
polyfills: ['zone.js'],
scripts: ['src/scripts/base.js'],
styles: ['src/styles/base.scss'],
assets: ['src/assets/base.png'],
};

process.env['NODE_ENV'] = 'development';
process.env['WEBPACK_SERVE'] = 'true';

const rspackConfig = createRspackConfig(options, executorContext);

expect(rspackConfig.context).toBe(
'/<workspace-root>/apps/the-project'
);
expect(rspackConfig.entry['main'].import).toEqual(
['src/main.ts']
);
expect(rspackConfig.output.uniqueName).toBe('the-project');
expect(rspackConfig.output.path).toBe(
'/<workspace-root>/apps/the-project/dist'
);
expect((rspackConfig.resolve.tsConfig as { configFile: string }).configFile).toBe(
'/<workspace-root>/apps/the-project/ts.config.json'
);

const hmrLoaderRule = rspackConfig.module.rules.find((rule: RuleSetRule) => rule.loader.endsWith('/build/src/lib/loaders/hmr/hmr-loader.ts')) as RuleSetRule;
expect(hmrLoaderRule.include).toEqual(
['/<workspace-root>/apps/the-project/src/main.ts']
);

const sassLoaderRule = rspackConfig.module.rules.find((rule: RuleSetRule) => (rule.test as RegExp)?.test('.scss')) as RuleSetRule;
expect(sassLoaderRule.use[0].options.sassOptions.loadPaths).toEqual(
[
'/<workspace-root>/apps/the-project/src/styles',
'/<workspace-root>/packages/common/styles',
]
);

expect(options.polyfills).toEqual(['zone.js']);
expect(options.scripts).toEqual(['src/scripts/base.js']);
expect(options.styles).toEqual(['src/styles/base.scss']);
expect(options.assets).toEqual(['src/assets/base.png']);

const compiler = new Compiler('context', { output: {}, resolve: rspackConfig.resolve } as RspackOptionsNormalized);
(rspackConfig.plugins[0] as NgRspackPlugin).apply(compiler);

// TODO remove the following tricky way to check the correctness of plugin options
expect(compiler.__internal__builtinPlugins.filter((plugin) => plugin.name === 'EntryPlugin')
.map(({ options: { entry } }: any) => entry))
.toEqual(['zone.js', 'src/styles/base.scss', 'src/scripts/base.js']);
expect(compiler.__internal__builtinPlugins.filter((plugin) => plugin.name === 'CopyRspackPlugin')
.map(({ options: { patterns }}: any) => patterns.map((pattern) => pattern.from)))
.toEqual([['/<workspace-root>/apps/the-project/src/assets/base.png']]);
expect(compiler.__internal__builtinPlugins.filter((plugin) => plugin.name === 'HtmlRspackPlugin')
.map(({ options }: any) => options.template))
.toEqual(['/<workspace-root>/apps/the-project/src/index.html'])
});
});
4 changes: 3 additions & 1 deletion packages/nx-plugin/src/utils/create-rspack-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ExecutorContext, joinPathFragments, workspaceRoot } from '@nx/devkit';
import { ExecutorContext, joinPathFragments } from '@nx/devkit';
import { Configuration } from '@rspack/core';
import { createConfig } from '@ng-rspack/build';
import { BuildExecutorSchema } from '../executors/build/schema';
Expand All @@ -7,6 +7,7 @@ export function createRspackConfig(
options: BuildExecutorSchema & { port?: number },
context: ExecutorContext
): Configuration {
const workspaceRoot = context.root;
const { root, name } = context.projectGraph.nodes[context.projectName].data;

process.env['NODE_ENV'] = options.mode;
Expand All @@ -21,6 +22,7 @@ export function createRspackConfig(
polyfills: options.polyfills ?? [],
assets: options.assets ?? [],
styles: options.styles ?? [],
stylePreprocessorOptions: options.stylePreprocessorOptions,
scripts: options.scripts ?? [],
port: options.port ?? 4200,
});
Expand Down
2 changes: 1 addition & 1 deletion packages/nx-plugin/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"include": ["src/**/*.ts", "../build/src/lib/utils/create-config.spec.ts"],
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
}
2 changes: 1 addition & 1 deletion packages/nx-plugin/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
, "../build/src/lib/utils/create-config.spec.ts" ]
}

0 comments on commit 46383dc

Please sign in to comment.