Skip to content

Commit

Permalink
refine cache; bugfix (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
indooorsman authored Mar 25, 2022
1 parent 7a92fa5 commit 1aa49d7
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 115 deletions.
14 changes: 8 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Plugin, PluginBuild } from 'esbuild';
import type { OnLoadResult, Plugin, PluginBuild } from 'esbuild';
import BuildCache from './lib/cache';

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

Expand Down Expand Up @@ -48,11 +49,11 @@ declare interface PluginOptions {
v2?: boolean;
root?: string;
package?: {
name: string,
main?: string,
module?: string,
version?: string
}
name: string;
main?: string;
module?: string;
version?: string;
};
}

declare interface BuildContext {
Expand All @@ -61,6 +62,7 @@ declare interface BuildContext {
packageRoot?: string;
log: (...args: any[]) => void;
relative: (to: string) => `.${string}`;
cache: BuildCache;
}

declare function CssModulesPlugin(options?: PluginOptions): Plugin;
Expand Down
66 changes: 52 additions & 14 deletions lib/cache.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,55 @@
const cacheHolder = { cache: new Map() };

module.exports = {
get(key) {
const ref = cacheHolder.cache.get(key);
if (ref) {
return ref.deref();
const { readFile } = require('fs/promises');
class BuildCache {
/**
* @param {(...args: any[]) => void} log
*/
constructor(log) {
this.log = log || ((...args) => console.log(...args));
/**
* @type {Map<string, {result: import('esbuild').OnLoadResult; input: string}}
*/
this.cache = new Map();
}
/**
* @description key should be absolute path
* @param {string} key
* @returns {Promise<import('esbuild').OnLoadResult|void>}
*/
async get(key) {
const cachedData = this.cache.get(key);
if (cachedData) {
this.log(`find cache data, check if input changed(${key})...`);
const input = await readFile(key, { encoding: 'utf8' });
if (input === cachedData.input) {
this.log(`input not changed, return cache(${key})`);
return cachedData.result;
}
this.log(`input changed(${key})`);
return void 0;
}
this.log(`cache data not found(${key})`);
return void 0;
}
/**
* @description key should be absolute path
* @param {string} key
* @param {import('esbuild').OnLoadResult} result
* @param {string} originContent
* @returns {Promise<void>}
*/
async set(key, result, originContent) {
const m = process.memoryUsage.rss();
if (m / 1024 / 1024 > 250) {
this.log('memory usage > 250M');
this.clear();
}
},
set(key, data) {
const wr = new WeakRef(data);
cacheHolder.cache.set(key, wr);
},
const input = originContent || (await readFile(key, { encoding: 'utf8' }));
this.cache.set(key, { input, result });
}
clear() {
cacheHolder.cache.clear();
this.log('clear cache');
this.cache.clear();
}
};
}

module.exports = BuildCache;
188 changes: 99 additions & 89 deletions lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ const {
} = require('./utils.js');
const cssHandler = require('@parcel/css');
const camelCase = require('lodash/camelCase');
const { v4 } = require('uuid');
const cache = require('./cache.js');
const BuildCache = require('./cache.js');

/**
* buildCssModulesJs
* @param {{fullPath: string; options: import('..').Options; digest: string; build: import('..').Build}} params
* @returns {Promise<{resolveDir: string; js: string; css: string; exports: Record<string, string>}>}
* @returns {Promise<{resolveDir: string; js: string; css: string; originCss: string; exports: Record<string, string>}>}
*/
const buildCssModulesJs = async ({ fullPath, options, build }) => {
const cssFileName = path.basename(fullPath); // e.g. xxx.module.css?esbuild-css-modules-plugin-building
Expand Down Expand Up @@ -93,11 +92,39 @@ export default new Proxy(${classNamesMapString}, {
return {
js,
css: cssWithSourceMap,
originCss: originCss.toString('utf8'),
exports,
resolveDir
};
};

/**
* prepareBuild
* @param {import('..').Build} build
* @param {import('..').Options} options
* @return {Promise<void>}
*/
const prepareBuild = async (build, options) => {
const buildId = getBuildId(build);
build.initialOptions.metafile = true;
const packageRoot = options.root;
const buildRoot = getRootDir(build);
const log = getLogger(build);
const cache = new BuildCache(log);
const relative = (to) => getRelativePath(build, to);

log(`root of this build(#${buildId}):`, buildRoot);

build.context = {
buildId,
buildRoot,
packageRoot,
log,
relative,
cache
};
};

/**
* onResolveModulesCss
* @description mark module(s).css as sideEffects and add namespace
Expand Down Expand Up @@ -146,24 +173,25 @@ const onResolveModulesCss = async (args, build) => {
*/
const onLoadModulesCss = async (build, options, args) => {
const { path: maybeFullPath, pluginData = {} } = args;
const { buildRoot, log, relative } = build.context;
const { buildRoot, log, cache } = build.context;
const absPath = path.isAbsolute(maybeFullPath)
? maybeFullPath
: path.resolve(buildRoot, maybeFullPath);
const rpath = relative(absPath);
const rpath = pluginData.relativePathToBuildRoot;

log(`loading ${relative(args.path)}${args.suffix}`);
log(`loading ${rpath}${args.suffix}`);

const cached = cache.get(absPath);
log(`checking cache for`, absPath);
const cached = await cache.get(absPath);
if (cached) {
log('return built cache for', rpath);
log('return build cache for', rpath);
return cached;
}

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

const { js, resolveDir, css, exports } = await buildCssModulesJs({
const { js, resolveDir, css, exports, originCss } = await buildCssModulesJs({
fullPath: absPath,
options,
digest,
Expand All @@ -182,7 +210,62 @@ const onLoadModulesCss = async (build, options, args) => {
contents: js,
loader: 'js'
};
cache.set(absPath, result);
await cache.set(absPath, result, originCss);
log(`add build result to cache for ${absPath}`);

return result;
};

/**
* onResolveBuiltModulesCss
* @param {import('esbuild').OnResolveArgs} args
* @param {import('..').Build} build
* @returns {Promise<import('esbuild').OnResolveResult>}
*/
const onResolveBuiltModulesCss = async (args, build) => {
const { path: p, pluginData = {} } = args;
const { relativePathToBuildRoot } = pluginData;

build.context?.log(`resolve virtual path ${p} to ${relativePathToBuildRoot}${builtCssSuffix}`);

/**
* @type {import('esbuild').OnResolveResult}
*/
const result = {
namespace: pluginNamespace,
path: relativePathToBuildRoot + builtCssSuffix,
external: false,
pluginData,
sideEffects: true,
pluginName
};

return result;
};

/**
* onLoadBuiltModulesCss
* @param {import('esbuild').OnLoadArgs} args
* @param {import('..').Build} build
* @returns {Promise<import('esbuild').OnLoadResult>}
*/
const onLoadBuiltModulesCss = async ({ pluginData }, build) => {
const { log, buildRoot } = build.context;
const { css, relativePathToBuildRoot } = pluginData;
const absPath = path.resolve(buildRoot, relativePathToBuildRoot);
const resolveDir = path.dirname(absPath);
log('loading built css for', relativePathToBuildRoot);

/**
* @type {import('esbuild').OnLoadResult}
*/
const result = {
contents: css,
loader: 'css',
pluginName,
resolveDir,
pluginData
};

return result;
};
Expand All @@ -194,8 +277,9 @@ const onLoadModulesCss = async (build, options, args) => {
* @param {import('esbuild').BuildResult} result
*/
const onEnd = async (build, options, result) => {
const { buildId, buildRoot } = build.context;
const { buildId, buildRoot, cache } = build.context;
const log = getLogger(build);
log('done');

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

/**
* prepareBuild
* @param {import('..').Build} build
* @param {import('..').Options} options
* @return {Promise<void>}
*/
const prepareBuild = async (build, options) => {
const buildId = getBuildId(build);
build.initialOptions.metafile = true;
const packageRoot = options.root;
const buildRoot = getRootDir(build);
const log = getLogger(build);
const relative = (to) => getRelativePath(build, to);

log(`root of this build(#${buildId}):`, buildRoot);

build.context = {
buildId,
buildRoot,
packageRoot,
log,
relative
};
};

/**
* onLoadBuiltModulesCss
* @param {import('esbuild').OnLoadArgs} args
* @param {import('..').Build} build
* @returns {Promise<import('esbuild').OnLoadResult>}
*/
const onLoadBuiltModulesCss = async ({ pluginData }, build) => {
const { log, buildRoot } = build.context;
const { css, relativePathToBuildRoot } = pluginData;
const absPath = path.resolve(buildRoot, relativePathToBuildRoot);
const resolveDir = path.dirname(absPath);
log('loading built css for', relativePathToBuildRoot);

/**
* @type {import('esbuild').OnLoadResult}
*/
const result = {
contents: css,
loader: 'css',
pluginName,
resolveDir,
pluginData
};

return result;
};

/**
* onResolveBuiltModulesCss
* @param {import('esbuild').OnResolveArgs} args
* @param {import('..').Build} build
* @returns {Promise<import('esbuild').OnResolveResult>}
*/
const onResolveBuiltModulesCss = async (args, build) => {
const { path: p, pluginData = {} } = args;

build.context?.log(`resolve built css: ${args.path}`);

/**
* @type {import('esbuild').OnResolveResult}
*/
const result = {
namespace: pluginNamespace,
path: p,
external: false,
pluginData,
sideEffects: true,
pluginName
};

return result;
};

/**
* setup
* @param {import('..').Build} build
Expand All @@ -326,14 +332,17 @@ const onResolveBuiltModulesCss = async (args, build) => {
const setup = async (build, options) => {
await prepareBuild(build, options);

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

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

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

// load virtual path xxx.module.css?esbuild-css-modules-plugin-built
build.onLoad(
{
filter: builtModulesCssRegExp,
Expand Down
2 changes: 1 addition & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const buildingCssSuffix = `?${pluginName}-building`;
const builtCssSuffix = `?${pluginName}-built`;
const builtCssSuffixRegExp = builtCssSuffix.replace('?', '\\?').replace(/\-/g, '\\-');
const modulesCssRegExp = /\.modules?\.css$/i;
const builtModulesCssRegExp = new RegExp(`\\.modules?\\.css${builtCssSuffixRegExp}`, 'i');
const builtModulesCssRegExp = new RegExp(`\\.modules?\\.css${builtCssSuffixRegExp}$`, 'i');

/**
* getLogger
Expand Down
Loading

0 comments on commit 1aa49d7

Please sign in to comment.