Skip to content

Commit

Permalink
feat(webpack-extensions): add ability to generate css vars named expo…
Browse files Browse the repository at this point in the history
…rts in manifest index (3.x) (#2119)
  • Loading branch information
barak007 authored Nov 1, 2021
1 parent ecb2fca commit a07187f
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 35 deletions.
24 changes: 6 additions & 18 deletions packages/webpack-extensions/src/create-metadata-stylesheet.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import {
Stylable,
StylableMeta,
valueMapping,
Imported,
CSSResolve,
JSResolve,
} from '@stylable/core';
import { Rule, ChildNode, AtRule } from 'postcss';
import { Stylable, StylableMeta, valueMapping } from '@stylable/core';
import type { Rule, ChildNode, AtRule } from 'postcss';
import type { Metadata, ResolvedImport } from './types';
import { hashContent } from './hash-content-util';

export function createMetadataForStylesheet(
stylable: Stylable,
content: string,
resourcePath: string,
exposeNamespaceMapping = true
) {
const meta = stylable.fileProcessor.processContent(content, resourcePath);

exposeNamespaceMapping = true,
meta = stylable.fileProcessor.processContent(content, resourcePath)
): Metadata {
const usedMeta = collectDependenciesDeep(stylable, meta);

const hashes = createContentHashPerMeta(usedMeta.keys());
Expand Down Expand Up @@ -110,11 +103,6 @@ export function createContentHashPerMeta(usedMeta: Iterable<StylableMeta>) {
return hashes;
}

export type ResolvedImport = {
stImport: Imported;
resolved: CSSResolve | JSResolve | null;
};

export function collectDependenciesDeep(
stylable: Stylable,
meta: StylableMeta,
Expand Down
50 changes: 34 additions & 16 deletions packages/webpack-extensions/src/stylable-manifest-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { basename } from 'path';
import { EOL } from 'os';
import webpack from 'webpack';
import { RawSource } from 'webpack-sources';
import { Stylable } from '@stylable/core';
import { Stylable, StylableMeta } from '@stylable/core';
import { resolveNamespace } from '@stylable/node';
import { createMetadataForStylesheet } from './create-metadata-stylesheet';
import { hashContent } from './hash-content-util';
import { ComponentsMetadata } from './component-metadata-builder';
import { Metadata, Manifest } from './types';
import { MetadataList, Manifest } from './types';

export interface Options {
outputType: 'manifest' | 'fs-manifest';
package: { name: string; version: string };
packageAlias: Record<string, string>;
contentHashLength?: number;
exposeNamespaceMapping: boolean;
generateCSSVarsExports: boolean;
resolveNamespace(namespace: string, filePath: string): string;
filterComponents(resourcePath: string): boolean;
getCompId(resourcePath: string): string;
Expand All @@ -30,6 +31,7 @@ const defaultOptions: Options = {
packageAlias: {},
resolveNamespace,
exposeNamespaceMapping: true,
generateCSSVarsExports: false,
filterComponents(resourcePath) {
return resourcePath.endsWith('.comp.st.css');
},
Expand All @@ -41,6 +43,12 @@ const defaultOptions: Options = {
},
};

export function generateCssVarsNamedExports(name: string, meta: StylableMeta) {
return Object.keys(meta.cssVars)
.map((varName) => `${varName} as --${name}-${varName.slice(2)}`)
.join(',');
}

export class StylableManifestPlugin {
private options: Options;
constructor(options: Partial<Options> = {}) {
Expand All @@ -61,31 +69,39 @@ export class StylableManifestPlugin {
resolveNamespace: this.options.resolveNamespace,
});

compiler.hooks.done.tap(this.constructor.name + ' stylable.initCache', () => stylable.initCache());
compiler.hooks.done.tap(this.constructor.name + ' stylable.initCache', () =>
stylable.initCache()
);

let metadata: Array<{ compId: string; metadata: Metadata }>;
let metadataList: MetadataList;
compiler.hooks.compilation.tap(this.constructor.name, (compilation) => {
compilation.hooks.optimizeModules.tap(this.constructor.name, (modules) => {
metadata = this.createModulesMetadata(compiler, stylable, modules);
metadataList = this.createModulesMetadata(compiler, stylable, [...modules]);
});

compiler.hooks.emit.tap(this.constructor.name, (compilation) =>
this.emitManifest(metadataList, compilation)
);
});

compiler.hooks.emit.tap(this.constructor.name, (compilation) =>
this.emitManifest(metadata, compilation)
this.emitManifest(metadataList, compilation)
);
}
private emitManifest(
metadata: { compId: string; metadata: Metadata }[],
compilation: webpack.compilation.Compilation
) {
const manifest = metadata.reduce<Manifest>(
(manifest, { compId, metadata }) => {
private emitManifest(metadataList: MetadataList, compilation: webpack.compilation.Compilation) {
const manifest = metadataList.reduce<Manifest>(
(manifest, { meta, compId, metadata }) => {
const cssVars = this.options.generateCSSVarsExports
? generateCssVarsNamedExports(compId, meta)
: null;
Object.assign(manifest.stylesheetMapping, metadata.stylesheetMapping);
Object.assign(manifest.namespaceMapping, metadata.namespaceMapping);
manifest.componentsEntries[compId] = metadata.entry;
manifest.componentsIndex += `:import{-st-from: ${JSON.stringify(
metadata.entry
)};-st-default: ${compId};} ${compId}{}${EOL}`;
)};-st-default: ${compId};${
cssVars ? `-st-named:${cssVars};` : ``
}} ${compId}{}${EOL}`;
return manifest;
},
{
Expand Down Expand Up @@ -125,7 +141,7 @@ export class StylableManifestPlugin {
compiler: webpack.compiler.Compiler,
stylable: Stylable,
modules: webpack.compilation.Module[]
) {
): MetadataList {
const stylableComps = modules.filter((module) => {
const resource = (module as any).resource;
return resource && this.options.filterComponents(resource);
Expand All @@ -134,14 +150,16 @@ export class StylableManifestPlugin {
return stylableComps.map((module) => {
const resource = (module as any).resource;
const source = compiler.inputFileSystem.readFileSync(resource).toString();

const meta = stylable.fileProcessor.processContent(source, resource);
return {
meta,
compId: this.options.getCompId(resource),
metadata: createMetadataForStylesheet(
stylable,
source,
resource,
this.options.exposeNamespaceMapping
this.options.exposeNamespaceMapping,
meta
),
};
});
Expand Down
3 changes: 2 additions & 1 deletion packages/webpack-extensions/src/stylable-metadata-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Stylable, StylableMeta, processNamespace } from '@stylable/core';
import { loader as webpackLoader } from 'webpack';
import findConfig from 'find-config';
import { getOptions } from 'loader-utils';
import { createMetadataForStylesheet, ResolvedImport } from './create-metadata-stylesheet';
import { createMetadataForStylesheet } from './create-metadata-stylesheet';
import { ResolvedImport } from './types';

let stylable: Stylable;
const getLocalConfig = loadLocalConfigLoader();
Expand Down
14 changes: 14 additions & 0 deletions packages/webpack-extensions/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { CSSResolve, Imported, JSResolve, StylableMeta } from '@stylable/core';

export interface Metadata {
entry: string;
usedMeta: Map<StylableMeta, ResolvedImport[]>;
stylesheetMapping: Record<string, string>;
namespaceMapping?: Record<string, string>;
}
Expand All @@ -12,3 +15,14 @@ export interface Manifest {
componentsEntries: Record<string, string>;
componentsIndex: string;
}

export type MetadataList = Array<{
meta: StylableMeta;
compId: string;
metadata: Metadata;
}>;

export type ResolvedImport = {
stImport: Imported;
resolved: CSSResolve | JSResolve | null;
};
61 changes: 61 additions & 0 deletions packages/webpack-extensions/test/e2e/manifest.css-vars.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { StylableProjectRunner } from '@stylable/e2e-test-kit';
import { expect } from 'chai';
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { hashContent } from '@stylable/webpack-extensions';
import { EOL } from 'os';

const project = 'manifest-plugin';
const projectDir = dirname(
require.resolve(`@stylable/webpack-extensions/test/e2e/projects/${project}/webpack.config`)
);

describe(`${project} - manifest`, () => {
const projectRunner = StylableProjectRunner.mochaSetup(
{
projectDir,
launchOptions: {
// headless: false
},
configName: 'webpack.css-vars.config',
},
before,
afterEach,
after
);

it('Should generate manifest for the current build', () => {
const assets = projectRunner.getBuildAssets();
const manifestKey = Object.keys(assets).find((key) => key.startsWith('stylable.manifest'))!;
const source = assets[manifestKey].source();

const compContent = readFileSync(
join(projectRunner.projectDir, 'Button.comp.st.css'),
'utf-8'
);
const commonContent = readFileSync(
join(projectRunner.projectDir, 'common.st.css'),
'utf-8'
);
const commonHash = hashContent(commonContent);
const compHash = hashContent(compContent);

expect(JSON.parse(source)).to.deep.include({
name: 'manifest-plugin-test',
version: '0.0.0-test',
componentsIndex: `:import{-st-from: "/${compHash}.st.css";-st-default: Button;-st-named:--myColor as --Button-myColor;} Button{}${EOL}`,
componentsEntries: { Button: `/${compHash}.st.css` },
stylesheetMapping: {
[`/${compHash}.st.css`]: compContent.replace(
'./common.st.css',
`/${commonHash}.st.css`
),
[`/${commonHash}.st.css`]: commonContent,
},
namespaceMapping: {
[`/${commonHash}.st.css`]: 'common911354609',
[`/${compHash}.st.css`]: 'Buttoncomp1090430236',
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
}

.root {
--myColor: red;
color: value(myColor)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const { stylableLoaders } = require('@stylable/experimental-loader');
const { StylableManifestPlugin } = require('@stylable/webpack-extensions');

/** @type {import('webpack').Configuration} */
module.exports = {
mode: 'development',
context: __dirname,
devtool: 'source-map',
entry: require.resolve('./index'),
output: {
library: 'metadata',
},
plugins: [
new StylableManifestPlugin({
package: require('./package.json'),
generateCSSVarsExports: true,
}),
],
module: {
rules: [
{
test: /\.st\.css?$/,
use: [stylableLoaders.transform({ exportsOnly: true })],
},
],
},
};

0 comments on commit a07187f

Please sign in to comment.