Skip to content

Commit 1aa49d7

Browse files
authored
refine cache; bugfix (#29)
1 parent 7a92fa5 commit 1aa49d7

File tree

6 files changed

+181
-115
lines changed

6 files changed

+181
-115
lines changed

index.d.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { Plugin, PluginBuild } from 'esbuild';
1+
import type { OnLoadResult, Plugin, PluginBuild } from 'esbuild';
2+
import BuildCache from './lib/cache';
23

34
declare type GenerateScopedNameFunction = (name: string, filename: string, css: string) => string;
45

@@ -48,11 +49,11 @@ declare interface PluginOptions {
4849
v2?: boolean;
4950
root?: string;
5051
package?: {
51-
name: string,
52-
main?: string,
53-
module?: string,
54-
version?: string
55-
}
52+
name: string;
53+
main?: string;
54+
module?: string;
55+
version?: string;
56+
};
5657
}
5758

5859
declare interface BuildContext {
@@ -61,6 +62,7 @@ declare interface BuildContext {
6162
packageRoot?: string;
6263
log: (...args: any[]) => void;
6364
relative: (to: string) => `.${string}`;
65+
cache: BuildCache;
6466
}
6567

6668
declare function CssModulesPlugin(options?: PluginOptions): Plugin;

lib/cache.js

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,55 @@
1-
const cacheHolder = { cache: new Map() };
2-
3-
module.exports = {
4-
get(key) {
5-
const ref = cacheHolder.cache.get(key);
6-
if (ref) {
7-
return ref.deref();
1+
const { readFile } = require('fs/promises');
2+
class BuildCache {
3+
/**
4+
* @param {(...args: any[]) => void} log
5+
*/
6+
constructor(log) {
7+
this.log = log || ((...args) => console.log(...args));
8+
/**
9+
* @type {Map<string, {result: import('esbuild').OnLoadResult; input: string}}
10+
*/
11+
this.cache = new Map();
12+
}
13+
/**
14+
* @description key should be absolute path
15+
* @param {string} key
16+
* @returns {Promise<import('esbuild').OnLoadResult|void>}
17+
*/
18+
async get(key) {
19+
const cachedData = this.cache.get(key);
20+
if (cachedData) {
21+
this.log(`find cache data, check if input changed(${key})...`);
22+
const input = await readFile(key, { encoding: 'utf8' });
23+
if (input === cachedData.input) {
24+
this.log(`input not changed, return cache(${key})`);
25+
return cachedData.result;
26+
}
27+
this.log(`input changed(${key})`);
28+
return void 0;
29+
}
30+
this.log(`cache data not found(${key})`);
31+
return void 0;
32+
}
33+
/**
34+
* @description key should be absolute path
35+
* @param {string} key
36+
* @param {import('esbuild').OnLoadResult} result
37+
* @param {string} originContent
38+
* @returns {Promise<void>}
39+
*/
40+
async set(key, result, originContent) {
41+
const m = process.memoryUsage.rss();
42+
if (m / 1024 / 1024 > 250) {
43+
this.log('memory usage > 250M');
44+
this.clear();
845
}
9-
},
10-
set(key, data) {
11-
const wr = new WeakRef(data);
12-
cacheHolder.cache.set(key, wr);
13-
},
46+
const input = originContent || (await readFile(key, { encoding: 'utf8' }));
47+
this.cache.set(key, { input, result });
48+
}
1449
clear() {
15-
cacheHolder.cache.clear();
50+
this.log('clear cache');
51+
this.cache.clear();
1652
}
17-
};
53+
}
54+
55+
module.exports = BuildCache;

lib/plugin.js

Lines changed: 99 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@ const {
1717
} = require('./utils.js');
1818
const cssHandler = require('@parcel/css');
1919
const camelCase = require('lodash/camelCase');
20-
const { v4 } = require('uuid');
21-
const cache = require('./cache.js');
20+
const BuildCache = require('./cache.js');
2221

2322
/**
2423
* buildCssModulesJs
2524
* @param {{fullPath: string; options: import('..').Options; digest: string; build: import('..').Build}} params
26-
* @returns {Promise<{resolveDir: string; js: string; css: string; exports: Record<string, string>}>}
25+
* @returns {Promise<{resolveDir: string; js: string; css: string; originCss: string; exports: Record<string, string>}>}
2726
*/
2827
const buildCssModulesJs = async ({ fullPath, options, build }) => {
2928
const cssFileName = path.basename(fullPath); // e.g. xxx.module.css?esbuild-css-modules-plugin-building
@@ -93,11 +92,39 @@ export default new Proxy(${classNamesMapString}, {
9392
return {
9493
js,
9594
css: cssWithSourceMap,
95+
originCss: originCss.toString('utf8'),
9696
exports,
9797
resolveDir
9898
};
9999
};
100100

101+
/**
102+
* prepareBuild
103+
* @param {import('..').Build} build
104+
* @param {import('..').Options} options
105+
* @return {Promise<void>}
106+
*/
107+
const prepareBuild = async (build, options) => {
108+
const buildId = getBuildId(build);
109+
build.initialOptions.metafile = true;
110+
const packageRoot = options.root;
111+
const buildRoot = getRootDir(build);
112+
const log = getLogger(build);
113+
const cache = new BuildCache(log);
114+
const relative = (to) => getRelativePath(build, to);
115+
116+
log(`root of this build(#${buildId}):`, buildRoot);
117+
118+
build.context = {
119+
buildId,
120+
buildRoot,
121+
packageRoot,
122+
log,
123+
relative,
124+
cache
125+
};
126+
};
127+
101128
/**
102129
* onResolveModulesCss
103130
* @description mark module(s).css as sideEffects and add namespace
@@ -146,24 +173,25 @@ const onResolveModulesCss = async (args, build) => {
146173
*/
147174
const onLoadModulesCss = async (build, options, args) => {
148175
const { path: maybeFullPath, pluginData = {} } = args;
149-
const { buildRoot, log, relative } = build.context;
176+
const { buildRoot, log, cache } = build.context;
150177
const absPath = path.isAbsolute(maybeFullPath)
151178
? maybeFullPath
152179
: path.resolve(buildRoot, maybeFullPath);
153-
const rpath = relative(absPath);
180+
const rpath = pluginData.relativePathToBuildRoot;
154181

155-
log(`loading ${relative(args.path)}${args.suffix}`);
182+
log(`loading ${rpath}${args.suffix}`);
156183

157-
const cached = cache.get(absPath);
184+
log(`checking cache for`, absPath);
185+
const cached = await cache.get(absPath);
158186
if (cached) {
159-
log('return built cache for', rpath);
187+
log('return build cache for', rpath);
160188
return cached;
161189
}
162190

163191
const hex = createHash('sha256').update(rpath).digest('hex');
164192
const digest = hex.slice(hex.length - 255, hex.length);
165193

166-
const { js, resolveDir, css, exports } = await buildCssModulesJs({
194+
const { js, resolveDir, css, exports, originCss } = await buildCssModulesJs({
167195
fullPath: absPath,
168196
options,
169197
digest,
@@ -182,7 +210,62 @@ const onLoadModulesCss = async (build, options, args) => {
182210
contents: js,
183211
loader: 'js'
184212
};
185-
cache.set(absPath, result);
213+
await cache.set(absPath, result, originCss);
214+
log(`add build result to cache for ${absPath}`);
215+
216+
return result;
217+
};
218+
219+
/**
220+
* onResolveBuiltModulesCss
221+
* @param {import('esbuild').OnResolveArgs} args
222+
* @param {import('..').Build} build
223+
* @returns {Promise<import('esbuild').OnResolveResult>}
224+
*/
225+
const onResolveBuiltModulesCss = async (args, build) => {
226+
const { path: p, pluginData = {} } = args;
227+
const { relativePathToBuildRoot } = pluginData;
228+
229+
build.context?.log(`resolve virtual path ${p} to ${relativePathToBuildRoot}${builtCssSuffix}`);
230+
231+
/**
232+
* @type {import('esbuild').OnResolveResult}
233+
*/
234+
const result = {
235+
namespace: pluginNamespace,
236+
path: relativePathToBuildRoot + builtCssSuffix,
237+
external: false,
238+
pluginData,
239+
sideEffects: true,
240+
pluginName
241+
};
242+
243+
return result;
244+
};
245+
246+
/**
247+
* onLoadBuiltModulesCss
248+
* @param {import('esbuild').OnLoadArgs} args
249+
* @param {import('..').Build} build
250+
* @returns {Promise<import('esbuild').OnLoadResult>}
251+
*/
252+
const onLoadBuiltModulesCss = async ({ pluginData }, build) => {
253+
const { log, buildRoot } = build.context;
254+
const { css, relativePathToBuildRoot } = pluginData;
255+
const absPath = path.resolve(buildRoot, relativePathToBuildRoot);
256+
const resolveDir = path.dirname(absPath);
257+
log('loading built css for', relativePathToBuildRoot);
258+
259+
/**
260+
* @type {import('esbuild').OnLoadResult}
261+
*/
262+
const result = {
263+
contents: css,
264+
loader: 'css',
265+
pluginName,
266+
resolveDir,
267+
pluginData
268+
};
186269

187270
return result;
188271
};
@@ -194,8 +277,9 @@ const onLoadModulesCss = async (build, options, args) => {
194277
* @param {import('esbuild').BuildResult} result
195278
*/
196279
const onEnd = async (build, options, result) => {
197-
const { buildId, buildRoot } = build.context;
280+
const { buildId, buildRoot, cache } = build.context;
198281
const log = getLogger(build);
282+
log('done');
199283

200284
if (options.inject === true || typeof options.inject === 'string') {
201285
const cssContents = [];
@@ -239,84 +323,6 @@ const onEnd = async (build, options, result) => {
239323
}
240324
};
241325

242-
/**
243-
* prepareBuild
244-
* @param {import('..').Build} build
245-
* @param {import('..').Options} options
246-
* @return {Promise<void>}
247-
*/
248-
const prepareBuild = async (build, options) => {
249-
const buildId = getBuildId(build);
250-
build.initialOptions.metafile = true;
251-
const packageRoot = options.root;
252-
const buildRoot = getRootDir(build);
253-
const log = getLogger(build);
254-
const relative = (to) => getRelativePath(build, to);
255-
256-
log(`root of this build(#${buildId}):`, buildRoot);
257-
258-
build.context = {
259-
buildId,
260-
buildRoot,
261-
packageRoot,
262-
log,
263-
relative
264-
};
265-
};
266-
267-
/**
268-
* onLoadBuiltModulesCss
269-
* @param {import('esbuild').OnLoadArgs} args
270-
* @param {import('..').Build} build
271-
* @returns {Promise<import('esbuild').OnLoadResult>}
272-
*/
273-
const onLoadBuiltModulesCss = async ({ pluginData }, build) => {
274-
const { log, buildRoot } = build.context;
275-
const { css, relativePathToBuildRoot } = pluginData;
276-
const absPath = path.resolve(buildRoot, relativePathToBuildRoot);
277-
const resolveDir = path.dirname(absPath);
278-
log('loading built css for', relativePathToBuildRoot);
279-
280-
/**
281-
* @type {import('esbuild').OnLoadResult}
282-
*/
283-
const result = {
284-
contents: css,
285-
loader: 'css',
286-
pluginName,
287-
resolveDir,
288-
pluginData
289-
};
290-
291-
return result;
292-
};
293-
294-
/**
295-
* onResolveBuiltModulesCss
296-
* @param {import('esbuild').OnResolveArgs} args
297-
* @param {import('..').Build} build
298-
* @returns {Promise<import('esbuild').OnResolveResult>}
299-
*/
300-
const onResolveBuiltModulesCss = async (args, build) => {
301-
const { path: p, pluginData = {} } = args;
302-
303-
build.context?.log(`resolve built css: ${args.path}`);
304-
305-
/**
306-
* @type {import('esbuild').OnResolveResult}
307-
*/
308-
const result = {
309-
namespace: pluginNamespace,
310-
path: p,
311-
external: false,
312-
pluginData,
313-
sideEffects: true,
314-
pluginName
315-
};
316-
317-
return result;
318-
};
319-
320326
/**
321327
* setup
322328
* @param {import('..').Build} build
@@ -326,14 +332,17 @@ const onResolveBuiltModulesCss = async (args, build) => {
326332
const setup = async (build, options) => {
327333
await prepareBuild(build, options);
328334

335+
// resolve xxx.module.css to xxx.module.css?esbuild-css-modules-plugin-building
329336
build.onResolve({ filter: modulesCssRegExp, namespace: 'file' }, async (args) => {
330337
return await onResolveModulesCss(args, build);
331338
});
332339

340+
// load xxx.module.css?esbuild-css-modules-plugin-building
333341
build.onLoad({ filter: modulesCssRegExp, namespace: pluginNamespace }, async (args) => {
334342
return await onLoadModulesCss(build, options, args);
335343
});
336344

345+
// resolve virtual path xxx.module.css?esbuild-css-modules-plugin-built
337346
build.onResolve(
338347
{
339348
filter: builtModulesCssRegExp,
@@ -344,6 +353,7 @@ const setup = async (build, options) => {
344353
}
345354
);
346355

356+
// load virtual path xxx.module.css?esbuild-css-modules-plugin-built
347357
build.onLoad(
348358
{
349359
filter: builtModulesCssRegExp,

lib/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const buildingCssSuffix = `?${pluginName}-building`;
77
const builtCssSuffix = `?${pluginName}-built`;
88
const builtCssSuffixRegExp = builtCssSuffix.replace('?', '\\?').replace(/\-/g, '\\-');
99
const modulesCssRegExp = /\.modules?\.css$/i;
10-
const builtModulesCssRegExp = new RegExp(`\\.modules?\\.css${builtCssSuffixRegExp}`, 'i');
10+
const builtModulesCssRegExp = new RegExp(`\\.modules?\\.css${builtCssSuffixRegExp}$`, 'i');
1111

1212
/**
1313
* getLogger

0 commit comments

Comments
 (0)