Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
askoufis committed May 20, 2024
1 parent 58bb8fa commit 17e3671
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 8 deletions.
6 changes: 6 additions & 0 deletions .changeset/popular-cups-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@vanilla-extract/webpack-plugin': patch
'@vanilla-extract/integration': patch
---

Testing vite node compiler in webpack plugin
24 changes: 19 additions & 5 deletions packages/integration/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ export interface Compiler {
filePath: string,
options?: {
outputCss?: boolean;
serializeVirtualCssPath?: (file: {
fileName: string;
source: string;
}) => string | Promise<string>;
},
): Promise<{ source: string; watchFiles: Set<string> }>;
getCssForFile(virtualCssFilePath: string): { filePath: string; css: string };
Expand All @@ -184,7 +188,11 @@ interface ProcessedVanillaFile {

export interface CreateCompilerOptions {
root: string;
cssImportSpecifier?: (filePath: string) => string;
cssImportSpecifier?: (
cssDepModuleId: string,
moduleId: string,
source: string,
) => string | Promise<string>;
identifiers?: IdentifierOption;
viteConfig?: ViteUserConfig;
/** @deprecated */
Expand Down Expand Up @@ -332,7 +340,9 @@ export const createCompiler = ({

const { cssDeps, watchFiles } = scanModule(moduleNode);

for (const cssDep of cssDeps) {
// Filter out falsy css deps, webpack plugin's extracted.js file has undefined cssDeps for
// some reason
for (const cssDep of cssDeps.filter(Boolean)) {
const cssDepModuleId = normalizePath(cssDep);
const cssObjs = cssByModuleId.get(cssDepModuleId);
const cachedCss = cssCache.get(cssDepModuleId);
Expand All @@ -343,9 +353,9 @@ export const createCompiler = ({
continue;
}

if (cssObjs) {
let css = '';
let css = '';

if (cssObjs) {
if (cssObjs.length > 0) {
css = transformCss({
localClassNames: Array.from(localClassNames),
Expand All @@ -368,7 +378,11 @@ export const createCompiler = ({

if (cssObjs || cachedCss?.css) {
cssImports.push(
`import '${cssImportSpecifier(cssDepModuleId)}';`,
`import '${await cssImportSpecifier(
cssDepModuleId,
moduleId,
css,
)}';`,
);
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/webpack-plugin/loader2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "dist/vanilla-extract-webpack-plugin-loader2.cjs.js",
"module": "dist/vanilla-extract-webpack-plugin-loader2.esm.js"
}
6 changes: 6 additions & 0 deletions packages/webpack-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"module": "./loader/dist/vanilla-extract-webpack-plugin-loader.esm.js",
"default": "./loader/dist/vanilla-extract-webpack-plugin-loader.cjs.js"
},
"./loader2": {
"module": "./loader/dist/vanilla-extract-webpack-plugin-loader2.esm.js",
"default": "./loader/dist/vanilla-extract-webpack-plugin-loader2.cjs.js"
},
"./virtualFileLoader": {
"module": "./virtualFileLoader/dist/vanilla-extract-webpack-plugin-virtualFileLoader.esm.js",
"default": "./virtualFileLoader/dist/vanilla-extract-webpack-plugin-virtualFileLoader.cjs.js"
Expand All @@ -31,6 +35,7 @@
"entrypoints": [
"index.ts",
"loader.ts",
"loader2.ts",
"virtualFileLoader.ts",
"next.ts",
"virtualNextFileLoader.ts"
Expand All @@ -39,6 +44,7 @@
"files": [
"/dist",
"/loader",
"/loader2",
"/virtualFileLoader",
"/next",
"/virtualNextFileLoader",
Expand Down
3 changes: 2 additions & 1 deletion packages/webpack-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Compiler } from 'webpack';
import { AbstractVanillaExtractPlugin } from './plugin';
// import { AbstractVanillaExtractPlugin } from './plugin';
import { AbstractVanillaExtractPlugin } from './plugin2';

export class VanillaExtractPlugin extends AbstractVanillaExtractPlugin {
apply(compiler: Compiler) {
Expand Down
4 changes: 2 additions & 2 deletions packages/webpack-plugin/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function pitch(this: LoaderContext) {
return;
}

log('Loading file');
log(`Loading file`);

const callback = this.async();

Expand Down Expand Up @@ -134,7 +134,7 @@ export function pitch(this: LoaderContext) {
},
});

log('Completed successfully');
log(`Completed successfully\n ${result}`);

callback(null, result);
})
Expand Down
77 changes: 77 additions & 0 deletions packages/webpack-plugin/src/loader2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// @ts-expect-error
import loaderUtils from 'loader-utils';
import {
getPackageInfo,
IdentifierOption,
transform,
type Compiler as VanillaExtractCompiler,
} from '@vanilla-extract/integration';

import type { LoaderContext } from './types';
import { debug, formatResourcePath } from './logger';
interface LoaderOptions {
outputCss: boolean;
identifiers?: IdentifierOption;
}

interface InternalLoaderOptions extends LoaderOptions {
vanillaExtractCompiler: VanillaExtractCompiler;
virtualLoader: 'virtualFileLoader' | 'virtualNextFileLoader';
}

const defaultIdentifierOption = (
mode: LoaderContext['mode'],
identifiers?: IdentifierOption,
): IdentifierOption =>
identifiers ?? (mode === 'production' ? 'short' : 'debug');

export default function (this: LoaderContext, source: string) {
const { identifiers } = loaderUtils.getOptions(this) as InternalLoaderOptions;

const { name } = getPackageInfo(this.rootContext);

const callback = this.async();

transform({
source,
filePath: this.resourcePath,
rootPath: this.rootContext,
packageName: name,
identOption: defaultIdentifierOption(this.mode, identifiers),
})
.then((code) => {
callback(null, code);
})
.catch((e) => {
callback(e);
});
}

export function pitch(this: LoaderContext) {
const { vanillaExtractCompiler, outputCss } = loaderUtils.getOptions(
this,
) as InternalLoaderOptions;

const log = debug(
`vanilla-extract:loader:${formatResourcePath(this.resourcePath)}`,
);

log(`Loading file ${this.resourcePath}`);

const callback = this.async();

vanillaExtractCompiler
.processVanillaFile(this.resourcePath, {
outputCss,
})
.then(({ source, watchFiles }) => {
watchFiles.forEach((dep) => {
this.addDependency(dep);
});
log(`Source:\n${source}`);
callback(null, source);
})
.catch((e) => {
callback(e);
});
}
188 changes: 188 additions & 0 deletions packages/webpack-plugin/src/plugin2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import {
cssFileFilter,
createCompiler,
IdentifierOption,
type Compiler,
serializeCss,
} from '@vanilla-extract/integration';
import type { Compiler as WebpackCompiler, RuleSetRule } from 'webpack';
// @ts-expect-error
import loaderUtils from 'loader-utils';

import createCompat, { WebpackCompat } from './compat';
import path from 'path';

const pluginName = 'VanillaExtractPlugin';

function markCSSFilesAsSideEffects(
compiler: WebpackCompiler,
compat: WebpackCompat,
) {
compiler.hooks.normalModuleFactory.tap(pluginName, (nmf) => {
if (compat.isWebpack5) {
nmf.hooks.createModule.tap(
pluginName,
// @ts-expect-error CreateData is typed as 'object'...
(createData: {
matchResource?: string;
settings: { sideEffects?: boolean };
}) => {
if (
createData.matchResource &&
(createData.matchResource.endsWith('.vanilla.css') ||
createData.matchResource.endsWith('vanilla.virtual.css'))
) {
createData.settings.sideEffects = true;
}
},
);
} else {
nmf.hooks.afterResolve.tap(
pluginName,
// @ts-expect-error Can't be typesafe for webpack 4
(result: {
matchResource?: string;
settings: { sideEffects?: boolean };
}) => {
if (
result.matchResource &&
(result.matchResource.endsWith('.vanilla.css') ||
result.matchResource.endsWith('vanilla.virtual.css'))
) {
result.settings.sideEffects = true;
}
},
);
}
});
}

export interface PluginOptions {
test?: RuleSetRule['test'];
identifiers?: IdentifierOption;
outputCss?: boolean;
externals?: any;
/** @deprecated */
allowRuntime?: boolean;
}

export abstract class AbstractVanillaExtractPlugin {
test: RuleSetRule['test'];
outputCss: boolean;
allowRuntime: boolean;
vanillaExtractCompiler?: Compiler;
identifiers?: IdentifierOption;

constructor(options: PluginOptions = {}) {
const {
test = cssFileFilter,
outputCss = true,
// Maybe pass externals to vite?
// externals,
allowRuntime,
identifiers,
} = options;

if (allowRuntime !== undefined) {
console.warn('The "allowRuntime" option is deprecated.');
}

this.test = test;
this.outputCss = outputCss;
this.allowRuntime = allowRuntime ?? false;
this.identifiers = identifiers;
}

protected inject(
compiler: WebpackCompiler,
virtualLoader: 'virtualFileLoader' | 'virtualNextFileLoader',
) {
const compat = createCompat(
Boolean(compiler.webpack && compiler.webpack.version),
);

markCSSFilesAsSideEffects(compiler, compat);

compiler.hooks.shutdown.tapPromise(pluginName, async () => {
console.log('Shutting down vanillaExtractCompiler');
await vanillaExtractCompiler.close();
});

const virtualFileLoader = require.resolve(
`@vanilla-extract/webpack-plugin/${virtualLoader}`,
);

const virtualFileLoaderExtractionFile = path.join(
path.dirname(require.resolve('../package.json')),
'extracted.js',
);

const virtualNextFileLoaderExtractionFile = path.join(
path.dirname(require.resolve('../package.json')),
'vanilla.virtual.css',
);

const vanillaExtractCompiler = createCompiler({
// Not sure if an absolute path will work here, hopefully it will
root: compiler.context,
identifiers: this.identifiers,
cssImportSpecifier: async (cssDepModuleId, moduleId, source) => {
const serializedCss = await serializeCss(source);
// `moduleId` won't change between css imports, so we should
const moduleContext = path.dirname(moduleId);

if (virtualLoader === 'virtualFileLoader') {
const relativeVirtualFileLoader = path.relative(
moduleContext,
virtualFileLoader,
);
const relativeVirtualFileLoaderExtractionFile = path.relative(
moduleContext,
virtualFileLoaderExtractionFile,
);
const relativeVirtualResourceLoader = `${relativeVirtualFileLoader}?${JSON.stringify(
{
source: serializedCss,
},
)}`;

const request = `${cssDepModuleId}.vanilla.css!=!${relativeVirtualResourceLoader}!${relativeVirtualFileLoaderExtractionFile}`;
return request;
} else {
// https://github.com/SukkaW/style9-webpack/blob/f51c46bbcd95ea3b988d3559c3b35cc056874366/src/next-appdir/style9-next-loader.ts#L64-L72
const relativeVirtualNextFileLoaderExtractionFile = path.relative(
moduleContext,
virtualNextFileLoaderExtractionFile,
);
const request =
// Next.js RSC CSS extraction will discard any loaders in the request.
// So we need to pass virtual css information through resourceQuery.
// https://github.com/vercel/next.js/blob/3a9bfe60d228fc2fd8fe65b76d49a0d21df4ecc7/packages/next/src/build/webpack/plugins/flight-client-entry-plugin.ts#L425-L429
// The compressed serialized CSS of vanilla-extract will add compressionFlag.
// Causing the resourceQuery to be abnormally split, so uri encoding is required.
// https://github.com/vanilla-extract-css/vanilla-extract/blob/58005eb5e7456cf2b3c04ea7aef29677db37cc3c/packages/integration/src/serialize.ts#L15
`${relativeVirtualNextFileLoaderExtractionFile}?${encodeURIComponent(
JSON.stringify({ source: serializedCss }),
)}`;
return request;
}
},
});
this.vanillaExtractCompiler = vanillaExtractCompiler;

compiler.options.module?.rules.splice(0, 0, {
test: this.test,
use: [
{
loader: require.resolve('../loader2'),
options: {
outputCss: this.outputCss,
identifiers: this.identifiers,
vanillaExtractCompiler,
virtualLoader: virtualLoader,
},
},
],
});
}
}
Loading

0 comments on commit 17e3671

Please sign in to comment.