From 077e57e77cae94a1d67ba92debaf0f5a21184d46 Mon Sep 17 00:00:00 2001 From: Dmitriy Nikiforov Date: Thu, 8 Dec 2022 13:55:44 +0500 Subject: [PATCH 1/2] refactor: implmenet TransformCacheCollection --- packages/babel/src/cache.ts | 13 ++++ packages/babel/src/evaluators/index.ts | 15 ++-- packages/babel/src/index.ts | 1 + packages/babel/src/module.ts | 25 +++++-- packages/babel/src/plugins/babel-transform.ts | 15 ++-- .../transform-stages/1-prepare-for-eval.ts | 38 ++++------ packages/babel/src/transform-stages/2-eval.ts | 10 ++- packages/babel/src/transform.ts | 37 +++------- packages/babel/src/types.ts | 2 - packages/cli/src/linaria.ts | 12 +--- packages/rollup/src/index.ts | 16 ++--- packages/testkit/src/module.test.ts | 70 ++++++++----------- packages/vite/src/index.ts | 21 +++--- 13 files changed, 118 insertions(+), 157 deletions(-) create mode 100644 packages/babel/src/cache.ts diff --git a/packages/babel/src/cache.ts b/packages/babel/src/cache.ts new file mode 100644 index 000000000..9cf53b44a --- /dev/null +++ b/packages/babel/src/cache.ts @@ -0,0 +1,13 @@ +import type Module from './module'; +import type { ITransformFileResult } from './types'; + +export class TransformCacheCollection { + constructor( + public readonly resolveCache: Map = new Map(), + public readonly codeCache: Map< + string, + Map + > = new Map(), + public readonly evalCache: Map = new Map() + ) {} +} diff --git a/packages/babel/src/evaluators/index.ts b/packages/babel/src/evaluators/index.ts index 10465cca0..57f7a0bd1 100644 --- a/packages/babel/src/evaluators/index.ts +++ b/packages/babel/src/evaluators/index.ts @@ -2,27 +2,20 @@ * This file is an entry point for module evaluation for getting lazy dependencies. */ +import type { TransformCacheCollection } from '../cache'; import Module from '../module'; import loadLinariaOptions from '../transform-stages/helpers/loadLinariaOptions'; -import type { Options, CodeCache } from '../types'; +import type { Options } from '../types'; export default function evaluate( - resolveCache: Map, - codeCache: CodeCache, - evalCache: Map, + cache: TransformCacheCollection, code: string[], options: Pick ) { const filename = options?.filename ?? 'unknown'; const pluginOptions = loadLinariaOptions(options.pluginOptions); - const m = new Module( - filename ?? 'unknown', - pluginOptions, - resolveCache, - codeCache, - evalCache - ); + const m = new Module(filename ?? 'unknown', pluginOptions, cache); m.dependencies = []; m.evaluate(code); diff --git a/packages/babel/src/index.ts b/packages/babel/src/index.ts index c8d7450ab..9e840c111 100644 --- a/packages/babel/src/index.ts +++ b/packages/babel/src/index.ts @@ -29,6 +29,7 @@ export { default as getVisitorKeys } from './utils/getVisitorKeys'; export type { VisitorKeys } from './utils/getVisitorKeys'; export { default as peek } from './utils/peek'; export { default as processTemplateExpression } from './utils/processTemplateExpression'; +export { TransformCacheCollection } from './cache'; function isEnabled(caller?: TransformCaller & { evaluate?: true }) { return caller?.name !== 'linaria' || caller.evaluate === true; diff --git a/packages/babel/src/module.ts b/packages/babel/src/module.ts index 22e75efb6..d710aca01 100644 --- a/packages/babel/src/module.ts +++ b/packages/babel/src/module.ts @@ -24,8 +24,9 @@ import type { BaseProcessor } from '@linaria/tags'; import type { StrictOptions } from '@linaria/utils'; import { getFileIdx } from '@linaria/utils'; +import { TransformCacheCollection } from './cache'; import * as process from './process'; -import type { CodeCache } from './types'; +import type { ITransformFileResult } from './types'; // Supported node builtins based on the modules polyfilled by webpack // `true` means module is polyfilled, `false` means module is empty @@ -121,12 +122,16 @@ class Module { debug: CustomDebug; + resolveCache: Map; + + codeCache: Map>; + + evalCache: Map; + constructor( filename: string, options: StrictOptions, - private resolveCache: Map, - private codeCache: CodeCache, - private evalCache: Map, + cache = new TransformCacheCollection(), private debuggerDepth = 0, private parentModule?: Module ) { @@ -140,6 +145,10 @@ class Module { this.transform = null; this.debug = createCustomDebug('module', this.idx); + this.resolveCache = cache.resolveCache; + this.codeCache = cache.codeCache; + this.evalCache = cache.evalCache; + Object.defineProperties(this, { id: { value: filename, @@ -338,9 +347,11 @@ class Module { m = new Module( filename, this.options, - this.resolveCache, - this.codeCache, - this.evalCache, + { + codeCache: this.codeCache, + evalCache: this.evalCache, + resolveCache: this.resolveCache, + }, this.debuggerDepth + 1, this ); diff --git a/packages/babel/src/plugins/babel-transform.ts b/packages/babel/src/plugins/babel-transform.ts index 372edb202..5b55dd203 100644 --- a/packages/babel/src/plugins/babel-transform.ts +++ b/packages/babel/src/plugins/babel-transform.ts @@ -6,10 +6,10 @@ import type { StrictOptions } from '@linaria/utils'; import { removeWithRelated, syncResolve } from '@linaria/utils'; import type { Core } from '../babel'; -import type Module from '../module'; +import { TransformCacheCollection } from '../cache'; import { prepareForEvalSync } from '../transform-stages/1-prepare-for-eval'; import evalStage from '../transform-stages/2-eval'; -import type { CodeCache, IPluginState } from '../types'; +import type { IPluginState } from '../types'; import processTemplateExpression from '../utils/processTemplateExpression'; import withLinariaMetadata from '../utils/withLinariaMetadata'; @@ -17,9 +17,7 @@ export default function collector( babel: Core, options: StrictOptions ): PluginObj { - const codeCache: CodeCache = new Map(); - const resolveCache = new Map(); - const evalCache = new Map(); + const cache = new TransformCacheCollection(); return { name: '@linaria/babel/babel-transform', @@ -34,8 +32,7 @@ export default function collector( const prepareStageResults = prepareForEvalSync( babel, - resolveCache, - codeCache, + cache, syncResolve, entryPoint, { @@ -52,9 +49,7 @@ export default function collector( } const evalStageResult = evalStage( - resolveCache, - codeCache, - evalCache, + cache, prepareStageResults.map((r) => r.code), { filename: file.opts.filename!, diff --git a/packages/babel/src/transform-stages/1-prepare-for-eval.ts b/packages/babel/src/transform-stages/1-prepare-for-eval.ts index 550d18e81..967ffed3d 100644 --- a/packages/babel/src/transform-stages/1-prepare-for-eval.ts +++ b/packages/babel/src/transform-stages/1-prepare-for-eval.ts @@ -8,8 +8,9 @@ import type { EvalRule, Evaluator } from '@linaria/utils'; import { buildOptions, getFileIdx, loadBabelOptions } from '@linaria/utils'; import type { Core } from '../babel'; +import type { TransformCacheCollection } from '../cache'; import type Module from '../module'; -import type { CodeCache, ITransformFileResult, Options } from '../types'; +import type { ITransformFileResult, Options } from '../types'; import withLinariaMetadata from '../utils/withLinariaMetadata'; import cachedParseSync from './helpers/cachedParseSync'; @@ -179,7 +180,7 @@ function processQueueItem( code: string; only: string[]; } | null, - codeCache: CodeCache, + cache: TransformCacheCollection, options: Pick ): | { @@ -191,7 +192,7 @@ function processQueueItem( if (!item) { return undefined; } - + const { codeCache } = cache; const pluginOptions = loadLinariaOptions(options.pluginOptions); const results = new Set(); @@ -272,14 +273,13 @@ function processQueueItem( export function prepareForEvalSync( babel: Core, - resolveCache: Map, - codeCache: CodeCache, + cache: TransformCacheCollection, resolve: (what: string, importer: string, stack: string[]) => string, resolvedFile: FileInQueue, options: Pick, stack: string[] = [] ): ITransformFileResult[] | undefined { - const processed = processQueueItem(babel, resolvedFile, codeCache, options); + const processed = processQueueItem(babel, resolvedFile, cache, options); if (!processed) return undefined; const { imports, name, results } = processed; @@ -292,7 +292,7 @@ export function prepareForEvalSync( try { const resolved = resolve(importedFile, name, stack); log('stage-1:sync-resolve', `✅ ${importedFile} -> ${resolved}`); - resolveCache.set( + cache.resolveCache.set( `${name} -> ${importedFile}`, `${resolved}\0${importsOnly.join(',')}` ); @@ -308,10 +308,7 @@ export function prepareForEvalSync( }); queue.forEach((item) => { - prepareForEvalSync(babel, resolveCache, codeCache, resolve, item, options, [ - name, - ...stack, - ]); + prepareForEvalSync(babel, cache, resolve, item, options, [name, ...stack]); }); return Array.from(results); @@ -325,8 +322,7 @@ const mutexes = new Map>(); */ export default async function prepareForEval( babel: Core, - resolveCache: Map, - codeCache: CodeCache, + cache: TransformCacheCollection, resolve: ( what: string, importer: string, @@ -342,7 +338,7 @@ export default async function prepareForEval( await mutex; } - const processed = processQueueItem(babel, resolvedFile, codeCache, options); + const processed = processQueueItem(babel, resolvedFile, cache, options); if (!processed) return undefined; const { imports, name, results } = processed; @@ -361,7 +357,7 @@ export default async function prepareForEval( log('stage-1:async-resolve', `✅ ${importedFile} -> ${resolved}`); const resolveCacheKey = `${name} -> ${importedFile}`; - const cached = resolveCache.get(resolveCacheKey); + const cached = cache.resolveCache.get(resolveCacheKey); const importsOnlySet = new Set(importsOnly); if (cached) { const [, cachedOnly] = cached.split('\0'); @@ -370,7 +366,7 @@ export default async function prepareForEval( }); } - resolveCache.set( + cache.resolveCache.set( resolveCacheKey, `${resolved}\0${[...importsOnlySet].join(',')}` ); @@ -392,15 +388,7 @@ export default async function prepareForEval( ); promises.push( - prepareForEval( - babel, - resolveCache, - codeCache, - resolve, - promise, - options, - [name, ...stack] - ) + prepareForEval(babel, cache, resolve, promise, options, [name, ...stack]) ); }); diff --git a/packages/babel/src/transform-stages/2-eval.ts b/packages/babel/src/transform-stages/2-eval.ts index 9b0745613..4c084d5f9 100644 --- a/packages/babel/src/transform-stages/2-eval.ts +++ b/packages/babel/src/transform-stages/2-eval.ts @@ -2,9 +2,9 @@ import { createCustomDebug } from '@linaria/logger'; import type { ValueCache } from '@linaria/tags'; import { getFileIdx } from '@linaria/utils'; +import type { TransformCacheCollection } from '../cache'; import evaluate from '../evaluators'; -import type Module from '../module'; -import type { CodeCache, Options } from '../types'; +import type { Options } from '../types'; import hasLinariaPreval from '../utils/hasLinariaPreval'; const wrap = (fn: () => T): T | Error => { @@ -19,9 +19,7 @@ const wrap = (fn: () => T): T | Error => { * Evaluates template dependencies. */ export default function evalStage( - resolveCache: Map, - codeCache: CodeCache, - evalCache: Map, + cache: TransformCacheCollection, code: string[], options: Pick ): [ValueCache, string[]] | null { @@ -29,7 +27,7 @@ export default function evalStage( log('stage-2', `>> evaluate __linariaPreval`); - const evaluated = evaluate(resolveCache, codeCache, evalCache, code, options); + const evaluated = evaluate(cache, code, options); const linariaPreval = hasLinariaPreval(evaluated.value) ? evaluated.value.__linariaPreval diff --git a/packages/babel/src/transform.ts b/packages/babel/src/transform.ts index e19cffd3e..337384a89 100644 --- a/packages/babel/src/transform.ts +++ b/packages/babel/src/transform.ts @@ -10,24 +10,22 @@ import type { TransformOptions } from '@babel/core'; import * as babel from '@babel/core'; -import type Module from './module'; +import { TransformCacheCollection } from './cache'; import prepareForEval, { prepareForEvalSync, } from './transform-stages/1-prepare-for-eval'; import evalStage from './transform-stages/2-eval'; import prepareForRuntime from './transform-stages/3-prepare-for-runtime'; import extractStage from './transform-stages/4-extract'; -import type { Options, Result, CodeCache, ITransformFileResult } from './types'; +import type { Options, Result, ITransformFileResult } from './types'; import withLinariaMetadata from './utils/withLinariaMetadata'; function syncStages( originalCode: string, options: Options, prepareStageResults: ITransformFileResult[] | undefined, - babelConfig: TransformOptions = {}, - resolveCache = new Map(), - codeCache: CodeCache = new Map(), - evalCache = new Map(), + babelConfig: TransformOptions, + cache: TransformCacheCollection, eventEmitter?: (ev: unknown) => void ) { const { filename } = options; @@ -48,9 +46,7 @@ function syncStages( eventEmitter?.({ type: 'transform:stage-2:start', filename }); const evalStageResult = evalStage( - resolveCache, - codeCache, - evalCache, + cache, prepareStageResults.map((r) => r.code), options ); @@ -116,13 +112,10 @@ export function transformSync( options: Options, syncResolve: (what: string, importer: string, stack: string[]) => string, babelConfig: TransformOptions = {}, - resolveCache = new Map(), - codeCache: CodeCache = new Map(), - evalCache = new Map(), + cache = new TransformCacheCollection(), eventEmitter?: (ev: unknown) => void ): Result { const { filename } = options; - // *** 1st stage *** eventEmitter?.({ type: 'transform:stage-1:start', filename }); @@ -135,8 +128,7 @@ export function transformSync( const prepareStageResults = prepareForEvalSync( babel, - resolveCache, - codeCache, + cache, syncResolve, entryPoint, options @@ -151,9 +143,7 @@ export function transformSync( options, prepareStageResults, babelConfig, - resolveCache, - codeCache, - evalCache, + cache, eventEmitter ); } @@ -167,9 +157,7 @@ export default async function transform( stack: string[] ) => Promise, babelConfig: TransformOptions = {}, - resolveCache = new Map(), - codeCache: CodeCache = new Map(), - evalCache = new Map(), + cache = new TransformCacheCollection(), eventEmitter?: (ev: unknown) => void ): Promise { const { filename } = options; @@ -186,8 +174,7 @@ export default async function transform( const prepareStageResults = await prepareForEval( babel, - resolveCache, - codeCache, + cache, asyncResolve, entryPoint, options @@ -202,9 +189,7 @@ export default async function transform( options, prepareStageResults, babelConfig, - resolveCache, - codeCache, - evalCache, + cache, eventEmitter ); } diff --git a/packages/babel/src/types.ts b/packages/babel/src/types.ts index 51038f043..fd7170e77 100644 --- a/packages/babel/src/types.ts +++ b/packages/babel/src/types.ts @@ -56,8 +56,6 @@ export interface ITransformFileResult { code: string; } -export type CodeCache = Map>; - export type Stage = 'preeval' | 'collect'; export type Location = { diff --git a/packages/cli/src/linaria.ts b/packages/cli/src/linaria.ts index 7c68a5359..55e6befa6 100644 --- a/packages/cli/src/linaria.ts +++ b/packages/cli/src/linaria.ts @@ -11,8 +11,7 @@ import mkdirp from 'mkdirp'; import normalize from 'normalize-path'; import yargs from 'yargs'; -import type { CodeCache, Module } from '@linaria/babel-preset'; -import { transform } from '@linaria/babel-preset'; +import { TransformCacheCollection, transform } from '@linaria/babel-preset'; import { asyncResolveFallback } from '@linaria/utils'; const modulesOptions = [ @@ -124,10 +123,7 @@ async function processFiles(files: (number | string)[], options: Options) { ], [] as string[] ); - - const codeCache: CodeCache = new Map(); - const resolveCache = new Map(); - const evalCache = new Map(); + const cache = new TransformCacheCollection(); const timings = new Map(); const addTiming = (key: string, value: number) => { @@ -180,9 +176,7 @@ async function processFiles(files: (number | string)[], options: Options) { }, asyncResolveFallback, {}, - resolveCache, - codeCache, - evalCache, + cache, onEvent ); diff --git a/packages/rollup/src/index.ts b/packages/rollup/src/index.ts index 1703e50b8..5ce9c5546 100644 --- a/packages/rollup/src/index.ts +++ b/packages/rollup/src/index.ts @@ -7,13 +7,15 @@ import { createFilter } from '@rollup/pluginutils'; import type { Plugin } from 'rollup'; -import { transform, slugify } from '@linaria/babel-preset'; +import { + transform, + slugify, + TransformCacheCollection, +} from '@linaria/babel-preset'; import type { PluginOptions, Preprocessor, Result, - CodeCache, - Module, } from '@linaria/babel-preset'; import { createCustomDebug } from '@linaria/logger'; import { getFileIdx } from '@linaria/utils'; @@ -36,9 +38,7 @@ export default function linaria({ }: RollupPluginOptions = {}): Plugin { const filter = createFilter(include, exclude); const cssLookup: { [key: string]: string } = {}; - const codeCache: CodeCache = new Map(); - const resolveCache = new Map(); - const evalCache = new Map(); + const cache = new TransformCacheCollection(); const plugin: Plugin = { name: 'linaria', @@ -89,9 +89,7 @@ export default function linaria({ }, asyncResolve, {}, - resolveCache, - codeCache, - evalCache + cache ); if (!result.cssText) return; diff --git a/packages/testkit/src/module.test.ts b/packages/testkit/src/module.test.ts index ee71c6f64..a98331515 100644 --- a/packages/testkit/src/module.test.ts +++ b/packages/testkit/src/module.test.ts @@ -3,7 +3,7 @@ import path from 'path'; import * as babel from '@babel/core'; import dedent from 'dedent'; -import { Module } from '@linaria/babel-preset'; +import { Module, TransformCacheCollection } from '@linaria/babel-preset'; import type { Evaluator, StrictOptions } from '@linaria/utils'; beforeEach(() => Module.invalidate()); @@ -38,17 +38,9 @@ const options: StrictOptions = { beforeEach(() => Module.invalidateEvalCache()); -function createModule( - filename: string, - opts: StrictOptions, - evalCache: Map = new Map() -) { - return new Module(filename, opts, new Map(), new Map(), evalCache); -} - it('creates module for JS files', () => { const filename = '/foo/bar/test.js'; - const mod = createModule(filename, options); + const mod = new Module(filename, options); mod.evaluate('module.exports = () => 42'); @@ -58,7 +50,7 @@ it('creates module for JS files', () => { }); it('requires JS files', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); mod.evaluate(dedent` const answer = require('./sample-script'); @@ -70,7 +62,7 @@ it('requires JS files', () => { }); it('requires JSON files', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); mod.evaluate(dedent` const data = require('./sample-data.json'); @@ -83,34 +75,34 @@ it('requires JSON files', () => { it('returns module from the cache', () => { const filename = getFileName(); - const cache = new Map(); - const mod = createModule(filename, options, cache); + const cache = new TransformCacheCollection(); + const mod = new Module(filename, options, cache); const id = './sample-data.json'; expect(mod.require(id)).toBe(mod.require(id)); - const res1 = createModule(filename, options, cache).require(id); - const res2 = createModule(filename, options, cache).require(id); + const res1 = new Module(filename, options, cache).require(id); + const res2 = new Module(filename, options, cache).require(id); expect(res1).toBe(res2); }); it('clears modules from the cache', () => { const filename = getFileName(); - const cache = new Map(); + const cache = new TransformCacheCollection(); const id = './sample-data.json'; - const result = createModule(filename, options, cache).require(id); + const result = new Module(filename, options, cache).require(id); - expect(createModule(filename, options, cache).require(id)).toBe(result); + expect(new Module(filename, options, cache).require(id)).toBe(result); - cache.clear(); + cache.evalCache.clear(); - expect(createModule(filename, options, cache).require(id)).not.toBe(result); + expect(new Module(filename, options, cache).require(id)).not.toBe(result); }); it('exports the path for non JS/JSON files', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(mod.require('./sample-asset.png')).toBe( path.join(__dirname, '__fixtures__', 'sample-asset.png') @@ -118,19 +110,19 @@ it('exports the path for non JS/JSON files', () => { }); it('returns module when requiring mocked builtin node modules', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(mod.require('path')).toBe(require('path')); }); it('returns null when requiring empty builtin node modules', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(mod.require('fs')).toBe(null); }); it('throws when requiring unmocked builtin node modules', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(() => mod.require('perf_hooks')).toThrow( 'Unable to import "perf_hooks". Importing Node builtins is not supported in the sandbox.' @@ -138,7 +130,7 @@ it('throws when requiring unmocked builtin node modules', () => { }); it('has access to the global object', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(() => mod.evaluate(dedent` @@ -148,7 +140,7 @@ it('has access to the global object', () => { }); it("doesn't have access to the process object", () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(() => mod.evaluate(dedent` @@ -158,7 +150,7 @@ it("doesn't have access to the process object", () => { }); it('has access to NODE_ENV', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); mod.evaluate(dedent` module.exports = process.env.NODE_ENV; @@ -168,7 +160,7 @@ it('has access to NODE_ENV', () => { }); it('has require.resolve available', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); mod.evaluate(dedent` module.exports = require.resolve('./sample-script'); @@ -180,7 +172,7 @@ it('has require.resolve available', () => { }); it('has require.ensure available', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(() => mod.evaluate(dedent` @@ -190,7 +182,7 @@ it('has require.ensure available', () => { }); it('has __filename available', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); mod.evaluate(dedent` module.exports = __filename; @@ -200,7 +192,7 @@ it('has __filename available', () => { }); it('has __dirname available', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); mod.evaluate(dedent` module.exports = __dirname; @@ -210,7 +202,7 @@ it('has __dirname available', () => { }); it('has setTimeout, clearTimeout available', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(() => mod.evaluate(dedent` @@ -224,7 +216,7 @@ it('has setTimeout, clearTimeout available', () => { }); it('has setInterval, clearInterval available', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(() => mod.evaluate(dedent` @@ -238,7 +230,7 @@ it('has setInterval, clearInterval available', () => { }); it('has setImmediate, clearImmediate available', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(() => mod.evaluate(dedent` @@ -252,7 +244,7 @@ it('has setImmediate, clearImmediate available', () => { }); it('has global objects available without referencing global', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); expect(() => mod.evaluate(dedent` @@ -266,7 +258,7 @@ it('changes resolve behaviour on overriding _resolveFilename', () => { Module._resolveFilename = (id) => (id === 'foo' ? 'bar' : id); - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); mod.evaluate(dedent` module.exports = [ @@ -283,7 +275,7 @@ it('changes resolve behaviour on overriding _resolveFilename', () => { it('correctly processes export declarations in strict mode', () => { const filename = '/foo/bar/test.js'; - const mod = createModule(filename, options); + const mod = new Module(filename, options); mod.evaluate('"use strict"; exports = module.exports = () => 42'); @@ -293,7 +285,7 @@ it('correctly processes export declarations in strict mode', () => { }); it('export * compiled by typescript to commonjs works', () => { - const mod = createModule(getFileName(), options); + const mod = new Module(getFileName(), options); mod.evaluate(dedent` const { foo } = require('./ts-compiled-re-exports'); diff --git a/packages/vite/src/index.ts b/packages/vite/src/index.ts index e5317c61e..52ce89fef 100644 --- a/packages/vite/src/index.ts +++ b/packages/vite/src/index.ts @@ -10,13 +10,12 @@ import { createFilter } from '@rollup/pluginutils'; import type { FilterPattern } from '@rollup/pluginutils'; import type { ModuleNode, Plugin, ResolvedConfig, ViteDevServer } from 'vite'; -import { transform, slugify } from '@linaria/babel-preset'; -import type { - PluginOptions, - Preprocessor, - CodeCache, - Module, +import { + transform, + slugify, + TransformCacheCollection, } from '@linaria/babel-preset'; +import type { PluginOptions, Preprocessor } from '@linaria/babel-preset'; import { createCustomDebug } from '@linaria/logger'; import { getFileIdx } from '@linaria/utils'; @@ -43,10 +42,8 @@ export default function linaria({ // const targets: { id: string; dependencies: string[] }[] = []; - const codeCache: CodeCache = new Map(); - const resolveCache = new Map(); - const evalCache = new Map(); - + const cache = new TransformCacheCollection(); + const { codeCache, resolveCache, evalCache } = cache; return { name: 'linaria', configResolved(resolvedConfig: ResolvedConfig) { @@ -140,9 +137,7 @@ export default function linaria({ }, asyncResolve, {}, - resolveCache, - codeCache, - evalCache + cache ); let { cssText, dependencies } = result; From 7b653d8eddd9e3b6ca9ad8a92041f209355c7062 Mon Sep 17 00:00:00 2001 From: Dmitriy Nikiforov Date: Sat, 10 Dec 2022 12:50:58 +0500 Subject: [PATCH 2/2] refactor: replace resolve with acquire --- examples/astro-solid/astro.config.js | 3 - packages/babel/src/module.ts | 8 +- packages/babel/src/plugins/babel-transform.ts | 11 +-- .../transform-stages/1-prepare-for-eval.ts | 74 +++++++++---------- packages/babel/src/transform.ts | 34 +++++---- packages/babel/src/types.ts | 5 ++ packages/babel/src/utils/createEntryPoint.ts | 9 +++ packages/cli/src/linaria.ts | 4 +- packages/esbuild/src/index.ts | 28 ++++--- packages/rollup/src/index.ts | 16 ++-- packages/stylelint/src/preprocessor.ts | 4 +- packages/utils/src/acquireFallback.ts | 27 +++++++ packages/utils/src/asyncResolveFallback.ts | 2 +- packages/utils/src/index.ts | 1 + packages/vite/src/index.ts | 28 ++++--- packages/webpack4-loader/src/index.ts | 29 +++++--- packages/webpack5-loader/src/index.ts | 33 ++++++--- 17 files changed, 191 insertions(+), 125 deletions(-) create mode 100644 packages/babel/src/utils/createEntryPoint.ts create mode 100644 packages/utils/src/acquireFallback.ts diff --git a/examples/astro-solid/astro.config.js b/examples/astro-solid/astro.config.js index 0dba18965..e88b277bd 100644 --- a/examples/astro-solid/astro.config.js +++ b/examples/astro-solid/astro.config.js @@ -20,9 +20,6 @@ export default defineConfig({ vite_linaria({ displayName: true, classNameSlug: (hash, title, args) => `${args.dir}_${title}_${hash}`, - babelOptions: { - presets: ['solid'], - }, }), vite_inspect(), ], diff --git a/packages/babel/src/module.ts b/packages/babel/src/module.ts index d710aca01..5b88fa9b7 100644 --- a/packages/babel/src/module.ts +++ b/packages/babel/src/module.ts @@ -131,7 +131,7 @@ class Module { constructor( filename: string, options: StrictOptions, - cache = new TransformCacheCollection(), + { codeCache, evalCache, resolveCache } = new TransformCacheCollection(), private debuggerDepth = 0, private parentModule?: Module ) { @@ -145,9 +145,9 @@ class Module { this.transform = null; this.debug = createCustomDebug('module', this.idx); - this.resolveCache = cache.resolveCache; - this.codeCache = cache.codeCache; - this.evalCache = cache.evalCache; + this.resolveCache = resolveCache; + this.codeCache = codeCache; + this.evalCache = evalCache; Object.defineProperties(this, { id: { diff --git a/packages/babel/src/plugins/babel-transform.ts b/packages/babel/src/plugins/babel-transform.ts index 5b55dd203..166af77ef 100644 --- a/packages/babel/src/plugins/babel-transform.ts +++ b/packages/babel/src/plugins/babel-transform.ts @@ -3,13 +3,14 @@ import type { NodePath } from '@babel/traverse'; import { debug } from '@linaria/logger'; import type { StrictOptions } from '@linaria/utils'; -import { removeWithRelated, syncResolve } from '@linaria/utils'; +import { removeWithRelated, syncAcquire } from '@linaria/utils'; import type { Core } from '../babel'; import { TransformCacheCollection } from '../cache'; import { prepareForEvalSync } from '../transform-stages/1-prepare-for-eval'; import evalStage from '../transform-stages/2-eval'; import type { IPluginState } from '../types'; +import { createEntryPoint } from '../utils/createEntryPoint'; import processTemplateExpression from '../utils/processTemplateExpression'; import withLinariaMetadata from '../utils/withLinariaMetadata'; @@ -24,16 +25,12 @@ export default function collector( pre(file: BabelFile) { debug('babel-transform:start', file.opts.filename); - const entryPoint = { - name: file.opts.filename!, - code: file.code, - only: ['__linariaPreval'], - }; + const entryPoint = createEntryPoint(file.opts.filename!, file.code); const prepareStageResults = prepareForEvalSync( babel, cache, - syncResolve, + syncAcquire, entryPoint, { root: file.opts.root ?? undefined, diff --git a/packages/babel/src/transform-stages/1-prepare-for-eval.ts b/packages/babel/src/transform-stages/1-prepare-for-eval.ts index 967ffed3d..423575f4b 100644 --- a/packages/babel/src/transform-stages/1-prepare-for-eval.ts +++ b/packages/babel/src/transform-stages/1-prepare-for-eval.ts @@ -1,4 +1,3 @@ -import { readFileSync } from 'fs'; import { dirname, extname } from 'path'; import type { BabelFileResult, TransformOptions } from '@babel/core'; @@ -10,7 +9,11 @@ import { buildOptions, getFileIdx, loadBabelOptions } from '@linaria/utils'; import type { Core } from '../babel'; import type { TransformCacheCollection } from '../cache'; import type Module from '../module'; -import type { ITransformFileResult, Options } from '../types'; +import type { + ExternalAcquireResult, + ITransformFileResult, + Options, +} from '../types'; import withLinariaMetadata from '../utils/withLinariaMetadata'; import cachedParseSync from './helpers/cachedParseSync'; @@ -173,22 +176,18 @@ function prepareCode( return [...result, preevalStageResult.metadata]; } +type ProcessQueueItemResult = { + imports: Map | null; + name: string; + results: ITransformFileResult[]; +}; + function processQueueItem( babel: Core, - item: { - name: string; - code: string; - only: string[]; - } | null, + item: FileInQueue | null, cache: TransformCacheCollection, options: Pick -): - | { - imports: Map | null; - name: string; - results: ITransformFileResult[]; - } - | undefined { +): ProcessQueueItemResult | undefined { if (!item) { return undefined; } @@ -274,7 +273,11 @@ function processQueueItem( export function prepareForEvalSync( babel: Core, cache: TransformCacheCollection, - resolve: (what: string, importer: string, stack: string[]) => string, + acquire: ( + what: string, + importer: string, + stack: string[] + ) => ExternalAcquireResult, resolvedFile: FileInQueue, options: Pick, stack: string[] = [] @@ -290,25 +293,24 @@ export function prepareForEvalSync( imports?.forEach((importsOnly, importedFile) => { try { - const resolved = resolve(importedFile, name, stack); - log('stage-1:sync-resolve', `✅ ${importedFile} -> ${resolved}`); + const { id, code } = acquire(importedFile, name, stack); + log('stage-1:sync-acquire', `✅ ${importedFile} -> ${id}`); cache.resolveCache.set( `${name} -> ${importedFile}`, - `${resolved}\0${importsOnly.join(',')}` + `${id}\0${importsOnly.join(',')}` ); - const fileContent = readFileSync(resolved, 'utf8'); queue.push({ - name: resolved, + name: id, only: importsOnly, - code: fileContent, + code, }); } catch (err) { - log('stage-1:sync-resolve', `❌ cannot resolve ${importedFile}: %O`, err); + log('stage-1:sync-acquire', `❌ cannot acquire ${importedFile}: %O`, err); } }); queue.forEach((item) => { - prepareForEvalSync(babel, cache, resolve, item, options, [name, ...stack]); + prepareForEvalSync(babel, cache, acquire, item, options, [name, ...stack]); }); return Array.from(results); @@ -323,11 +325,11 @@ const mutexes = new Map>(); export default async function prepareForEval( babel: Core, cache: TransformCacheCollection, - resolve: ( + acquire: ( what: string, importer: string, stack: string[] - ) => Promise, + ) => Promise, file: Promise, options: Pick, stack: string[] = [] @@ -348,14 +350,15 @@ export default async function prepareForEval( const promises: Promise[] = []; imports?.forEach((importsOnly, importedFile) => { - const promise = resolve(importedFile, name, stack).then( + const promise = acquire(importedFile, name, stack).then( (resolved) => { if (resolved === null) { - log('stage-1:resolve', `✅ ${importedFile} is ignored`); + log('stage-1:acquire', `✅ ${importedFile} is ignored`); return null; } + const { code, id } = resolved; - log('stage-1:async-resolve', `✅ ${importedFile} -> ${resolved}`); + log('stage-1:acquire', `✅ ${importedFile} -> ${id}`); const resolveCacheKey = `${name} -> ${importedFile}`; const cached = cache.resolveCache.get(resolveCacheKey); const importsOnlySet = new Set(importsOnly); @@ -368,27 +371,22 @@ export default async function prepareForEval( cache.resolveCache.set( resolveCacheKey, - `${resolved}\0${[...importsOnlySet].join(',')}` + `${id}\0${[...importsOnlySet].join(',')}` ); - const fileContent = readFileSync(resolved, 'utf8'); return { - name: resolved, + name: id, only: importsOnly, - code: fileContent, + code, }; }, (err: unknown) => { - log( - 'stage-1:async-resolve', - `❌ cannot resolve ${importedFile}: %O`, - err - ); + log('stage-1:acquire', `❌ cannot resolve ${importedFile}: %O`, err); return null; } ); promises.push( - prepareForEval(babel, cache, resolve, promise, options, [name, ...stack]) + prepareForEval(babel, cache, acquire, promise, options, [name, ...stack]) ); }); diff --git a/packages/babel/src/transform.ts b/packages/babel/src/transform.ts index 337384a89..9b9a9a413 100644 --- a/packages/babel/src/transform.ts +++ b/packages/babel/src/transform.ts @@ -17,7 +17,13 @@ import prepareForEval, { import evalStage from './transform-stages/2-eval'; import prepareForRuntime from './transform-stages/3-prepare-for-runtime'; import extractStage from './transform-stages/4-extract'; -import type { Options, Result, ITransformFileResult } from './types'; +import type { + Options, + Result, + ITransformFileResult, + ExternalAcquireResult, +} from './types'; +import { createEntryPoint } from './utils/createEntryPoint'; import withLinariaMetadata from './utils/withLinariaMetadata'; function syncStages( @@ -110,7 +116,11 @@ function syncStages( export function transformSync( originalCode: string, options: Options, - syncResolve: (what: string, importer: string, stack: string[]) => string, + acquire: ( + what: string, + importer: string, + stack: string[] + ) => ExternalAcquireResult, babelConfig: TransformOptions = {}, cache = new TransformCacheCollection(), eventEmitter?: (ev: unknown) => void @@ -120,16 +130,12 @@ export function transformSync( eventEmitter?.({ type: 'transform:stage-1:start', filename }); - const entryPoint = { - name: options.filename, - code: originalCode, - only: ['__linariaPreval'], - }; + const entryPoint = createEntryPoint(filename, originalCode); const prepareStageResults = prepareForEvalSync( babel, cache, - syncResolve, + acquire, entryPoint, options ); @@ -151,11 +157,11 @@ export function transformSync( export default async function transform( originalCode: string, options: Options, - asyncResolve: ( + aqcuire: ( what: string, importer: string, stack: string[] - ) => Promise, + ) => Promise, babelConfig: TransformOptions = {}, cache = new TransformCacheCollection(), eventEmitter?: (ev: unknown) => void @@ -166,16 +172,12 @@ export default async function transform( eventEmitter?.({ type: 'transform:stage-1:start', filename }); - const entryPoint = Promise.resolve({ - name: filename, - code: originalCode, - only: ['__linariaPreval'], - }); + const entryPoint = Promise.resolve(createEntryPoint(filename, originalCode)); const prepareStageResults = await prepareForEval( babel, cache, - asyncResolve, + aqcuire, entryPoint, options ); diff --git a/packages/babel/src/types.ts b/packages/babel/src/types.ts index fd7170e77..00c5caa07 100644 --- a/packages/babel/src/types.ts +++ b/packages/babel/src/types.ts @@ -96,3 +96,8 @@ export type MissedBabelCoreTypes = { file: { code: string; ast: File } ) => { path: NodePath }; }; + +export type ExternalAcquireResult = { + id: string; + code: string; +}; diff --git a/packages/babel/src/utils/createEntryPoint.ts b/packages/babel/src/utils/createEntryPoint.ts new file mode 100644 index 000000000..fd4d73266 --- /dev/null +++ b/packages/babel/src/utils/createEntryPoint.ts @@ -0,0 +1,9 @@ +export const createEntryPoint = ( + name: string, + code: string, + only = ['__linariaPreval'] +) => ({ + name, + code, + only, +}); diff --git a/packages/cli/src/linaria.ts b/packages/cli/src/linaria.ts index 55e6befa6..42be884d3 100644 --- a/packages/cli/src/linaria.ts +++ b/packages/cli/src/linaria.ts @@ -12,7 +12,7 @@ import normalize from 'normalize-path'; import yargs from 'yargs'; import { TransformCacheCollection, transform } from '@linaria/babel-preset'; -import { asyncResolveFallback } from '@linaria/utils'; +import { asyncAcquire } from '@linaria/utils'; const modulesOptions = [ 'commonjs', @@ -174,7 +174,7 @@ async function processFiles(files: (number | string)[], options: Options) { }, root: options.sourceRoot, }, - asyncResolveFallback, + asyncAcquire, {}, cache, onEvent diff --git a/packages/esbuild/src/index.ts b/packages/esbuild/src/index.ts index 6117baaf9..67ff00416 100644 --- a/packages/esbuild/src/index.ts +++ b/packages/esbuild/src/index.ts @@ -4,14 +4,18 @@ * returns transformed code without template literals and attaches generated source maps */ -import fs from 'fs'; +import fs from 'fs/promises'; import path from 'path'; import type { Plugin, TransformOptions, Loader } from 'esbuild'; -import { transformSync } from 'esbuild'; +import { transform as esbuildTransform } from 'esbuild'; -import type { PluginOptions, Preprocessor } from '@linaria/babel-preset'; -import { slugify, transform } from '@linaria/babel-preset'; +import type { + ExternalAcquireResult, + PluginOptions, + Preprocessor, +} from '@linaria/babel-preset'; +import { slugify, transform as linariaTransform } from '@linaria/babel-preset'; type EsbuildPluginOptions = { sourceMap?: boolean; @@ -33,10 +37,10 @@ export default function linaria({ setup(build) { const cssLookup = new Map(); - const asyncResolve = async ( + const acquire = async ( token: string, importer: string - ): Promise => { + ): Promise => { const context = path.isAbsolute(importer) ? path.dirname(importer) : path.join(process.cwd(), path.dirname(importer)); @@ -48,8 +52,8 @@ export default function linaria({ if (result.errors.length > 0) { throw new Error(`Cannot resolve ${token}`); } - - return result.path; + const code = await fs.readFile(result.path, 'utf8'); + return { id: result.path, code }; }; build.onResolve({ filter: /\.linaria\.css$/ }, (args) => { @@ -68,7 +72,7 @@ export default function linaria({ }); build.onLoad({ filter: /\.(js|jsx|ts|tsx)$/ }, async (args) => { - const rawCode = fs.readFileSync(args.path, 'utf8'); + const rawCode = await fs.readFile(args.path, 'utf8'); const { ext, name: filename } = path.parse(args.path); const loader = ext.replace(/^\./, '') as Loader; @@ -89,7 +93,7 @@ export default function linaria({ } } - const transformed = transformSync(rawCode, { + const transformed = await esbuildTransform(rawCode, { ...options, sourcefile: args.path, sourcemap: sourceMap, @@ -102,14 +106,14 @@ export default function linaria({ code += `/*# sourceMappingURL=data:application/json;base64,${esbuildMap}*/`; } - const result = await transform( + const result = await linariaTransform( code, { filename: args.path, preprocessor, pluginOptions: rest, }, - asyncResolve + acquire ); if (!result.cssText) { diff --git a/packages/rollup/src/index.ts b/packages/rollup/src/index.ts index 5ce9c5546..fe9da1637 100644 --- a/packages/rollup/src/index.ts +++ b/packages/rollup/src/index.ts @@ -16,6 +16,7 @@ import type { PluginOptions, Preprocessor, Result, + ExternalAcquireResult, } from '@linaria/babel-preset'; import { createCustomDebug } from '@linaria/logger'; import { getFileIdx } from '@linaria/utils'; @@ -60,20 +61,25 @@ export default function linaria({ log('rollup-init', id); - const asyncResolve = async (what: string, importer: string) => { + const acquire = async ( + what: string, + importer: string + ): Promise => { const resolved = await this.resolve(what, importer); if (resolved) { log('resolve', "✅ '%s'@'%s -> %O\n%s", what, importer, resolved); // Vite adds param like `?v=667939b3` to cached modules - const resolvedId = resolved.id.split('?')[0]; + const [acquiredId] = resolved.id.split('?'); - if (resolvedId.startsWith('\0')) { + if (acquiredId.startsWith('\0')) { // \0 is a special character in Rollup that tells Rollup to not include this in the bundle // https://rollupjs.org/guide/en/#outputexports return null; } - return resolvedId; + const module = await this.load({ id: acquiredId }); + if (module?.code == null) return null; + return { id: acquiredId, code: module.code }; } log('resolve', "❌ '%s'@'%s", what, importer); @@ -87,7 +93,7 @@ export default function linaria({ preprocessor, pluginOptions: rest, }, - asyncResolve, + acquire, {}, cache ); diff --git a/packages/stylelint/src/preprocessor.ts b/packages/stylelint/src/preprocessor.ts index 90c3a1b3b..9d2f031d1 100644 --- a/packages/stylelint/src/preprocessor.ts +++ b/packages/stylelint/src/preprocessor.ts @@ -2,7 +2,7 @@ import stripAnsi from 'strip-ansi'; import { transform } from '@linaria/babel-preset'; import type { Replacement } from '@linaria/babel-preset'; -import { asyncResolveFallback } from '@linaria/utils'; +import { asyncAcquire } from '@linaria/utils'; type Errors = { [key: string]: @@ -63,7 +63,7 @@ function preprocessor() { { filename, }, - asyncResolveFallback + asyncAcquire ); cache[filename] = undefined; diff --git a/packages/utils/src/acquireFallback.ts b/packages/utils/src/acquireFallback.ts new file mode 100644 index 000000000..5f8dead19 --- /dev/null +++ b/packages/utils/src/acquireFallback.ts @@ -0,0 +1,27 @@ +import { readFileSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; + +import asyncResolve, { syncResolve } from './asyncResolveFallback'; + +const encoding = 'utf8'; +export const syncAcquire = ( + what: string, + importer: string, + stack: string[] +) => { + const id = syncResolve(what, importer, stack); + const code = readFileSync(id, encoding); + return { id, code }; +}; + +export const asyncAcquire = async ( + what: string, + importer: string, + stack: string[] +) => { + const id = await asyncResolve(what, importer, stack); + const code = await readFile(id, encoding); + return { id, code }; +}; + +export default asyncAcquire; diff --git a/packages/utils/src/asyncResolveFallback.ts b/packages/utils/src/asyncResolveFallback.ts index b6ce583a0..0600d9f72 100644 --- a/packages/utils/src/asyncResolveFallback.ts +++ b/packages/utils/src/asyncResolveFallback.ts @@ -1,4 +1,4 @@ -import path from 'path'; +import path from 'node:path'; const safeResolve = (name: string, where: string[]): string | Error => { try { diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 597be41c8..f1106ca3d 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -3,6 +3,7 @@ export { default as asyncResolveFallback, syncResolve, } from './asyncResolveFallback'; +export * from './acquireFallback'; export { default as collectExportsAndImports } from './collectExportsAndImports'; export * from './collectExportsAndImports'; export { default as findIdentifiers, nonType } from './findIdentifiers'; diff --git a/packages/vite/src/index.ts b/packages/vite/src/index.ts index 52ce89fef..3272797eb 100644 --- a/packages/vite/src/index.ts +++ b/packages/vite/src/index.ts @@ -43,9 +43,10 @@ export default function linaria({ // const targets: { id: string; dependencies: string[] }[] = []; const cache = new TransformCacheCollection(); - const { codeCache, resolveCache, evalCache } = cache; + const { codeCache, evalCache } = cache; return { name: 'linaria', + enforce: 'post', configResolved(resolvedConfig: ResolvedConfig) { config = resolvedConfig; }, @@ -103,31 +104,36 @@ export default function linaria({ log('rollup-init', id); - const asyncResolve = async (what: string, importer: string) => { + const acquire = async (what: string, importer: string) => { const resolved = await this.resolve(what, importer); if (resolved) { log('resolve', "✅ '%s'@'%s -> %O\n%s", what, importer, resolved); // Vite adds param like `?v=667939b3` to cached modules - const resolvedId = resolved.id.split('?')[0]; + const [acquiredId] = resolved.id.split('?'); - if (resolvedId.startsWith('\0')) { + if (acquiredId.startsWith('\0')) { // \0 is a special character in Rollup that tells Rollup to not include this in the bundle // https://rollupjs.org/guide/en/#outputexports return null; } - return resolvedId; + if (devServer) { + const transformResult = await devServer.transformRequest( + acquiredId + ); + if (transformResult?.code == null) return null; + return { id: acquiredId, code: transformResult.code }; + } + + const module = await this.load({ id: acquiredId }); + if (module?.code == null) return null; + return { id: acquiredId, code: module.code }; } log('resolve', "❌ '%s'@'%s", what, importer); throw new Error(`Could not resolve ${what}`); }; - // TODO: Vite surely has some already transformed modules, solid - // why would we transform it again? - // We could provide some thing like `pretransform` and ask Vite to return transformed module - // (module.transformResult) - // So we don't need to duplicate babel plugins. const result = await transform( code, { @@ -135,7 +141,7 @@ export default function linaria({ preprocessor, pluginOptions: rest, }, - asyncResolve, + acquire, {}, cache ); diff --git a/packages/webpack4-loader/src/index.ts b/packages/webpack4-loader/src/index.ts index e8c64a09f..ab3faf485 100644 --- a/packages/webpack4-loader/src/index.ts +++ b/packages/webpack4-loader/src/index.ts @@ -9,7 +9,7 @@ import path from 'path'; import loaderUtils from 'loader-utils'; import type { RawSourceMap } from 'source-map'; -import type { Result } from '@linaria/babel-preset'; +import type { ExternalAcquireResult, Result } from '@linaria/babel-preset'; import { transform } from '@linaria/babel-preset'; import { debug } from '@linaria/logger'; @@ -49,17 +49,23 @@ export default function webpack4Loader( const outputFileName = this.resourcePath.replace(/\.[^.]+$/, extension); - const asyncResolve = (token: string, importer: string): Promise => { + const acquire = ( + token: string, + importer: string + ): Promise => { const context = path.isAbsolute(importer) ? path.dirname(importer) : path.join(process.cwd(), path.dirname(importer)); return new Promise((resolve, reject) => { - this.resolve(context, token, (err, result) => { - if (err) { - reject(err); - } else if (result) { - this.addDependency(result); - resolve(result); + this.resolve(context, token, (resolveError, id) => { + if (resolveError) { + reject(resolveError); + } else if (id) { + this.addDependency(id); + this.loadModule(id, (loadErr, code) => { + if (loadErr) reject(loadErr); + resolve({ id, code }); + }); } else { reject(new Error(`Cannot resolve ${token}`)); } @@ -75,7 +81,7 @@ export default function webpack4Loader( pluginOptions: rest, preprocessor, }, - asyncResolve + acquire ).then( async (result: Result) => { if (result.cssText) { @@ -88,9 +94,8 @@ export default function webpack4Loader( } await Promise.all( - result.dependencies?.map((dep) => - asyncResolve(dep, this.resourcePath) - ) ?? [] + result.dependencies?.map((dep) => acquire(dep, this.resourcePath)) ?? + [] ); try { diff --git a/packages/webpack5-loader/src/index.ts b/packages/webpack5-loader/src/index.ts index 2fd30a687..2ce028e97 100644 --- a/packages/webpack5-loader/src/index.ts +++ b/packages/webpack5-loader/src/index.ts @@ -9,7 +9,11 @@ import path from 'path'; import type { RawSourceMap } from 'source-map'; import type { RawLoaderDefinitionFunction } from 'webpack'; -import type { Result, Preprocessor } from '@linaria/babel-preset'; +import type { + Result, + Preprocessor, + ExternalAcquireResult, +} from '@linaria/babel-preset'; import { transform } from '@linaria/babel-preset'; import { debug } from '@linaria/logger'; @@ -62,17 +66,23 @@ const webpack5Loader: Loader = function webpack5LoaderPlugin( const outputFileName = this.resourcePath.replace(/\.[^.]+$/, extension); - const asyncResolve = (token: string, importer: string): Promise => { + const acquire = ( + token: string, + importer: string + ): Promise => { const context = path.isAbsolute(importer) ? path.dirname(importer) : path.join(process.cwd(), path.dirname(importer)); return new Promise((resolve, reject) => { - this.resolve(context, token, (err, result) => { - if (err) { - reject(err); - } else if (result) { - this.addDependency(result); - resolve(result); + this.resolve(context, token, (resolveError, id) => { + if (resolveError) { + reject(resolveError); + } else if (id) { + this.addDependency(id); + this.loadModule(id, (loadErr, code) => { + if (loadErr) reject(loadErr); + resolve({ id, code }); + }); } else { reject(new Error(`Cannot resolve ${token}`)); } @@ -88,7 +98,7 @@ const webpack5Loader: Loader = function webpack5LoaderPlugin( pluginOptions: rest, preprocessor, }, - asyncResolve + acquire ).then( async (result: Result) => { if (result.cssText) { @@ -101,9 +111,8 @@ const webpack5Loader: Loader = function webpack5LoaderPlugin( } await Promise.all( - result.dependencies?.map((dep) => - asyncResolve(dep, this.resourcePath) - ) ?? [] + result.dependencies?.map((dep) => acquire(dep, this.resourcePath)) ?? + [] ); try {