Skip to content

Commit c615441

Browse files
authored
Refactor v 2 2 (#23)
* refactor * v2.2.0 * add v2 inject testing * update readme
1 parent d12632a commit c615441

File tree

11 files changed

+515
-272
lines changed

11 files changed

+515
-272
lines changed

index.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ declare interface CssModulesOptions {
4141
}
4242

4343
declare interface PluginOptions {
44-
inject?: boolean;
44+
inject?: boolean | string | ((css: string, digest: string) => string);
4545
localsConvention?: CssModulesOptions['localsConvention'];
4646
generateScopedName?: CssModulesOptions['generateScopedName'];
4747
cssModulesOption?: CssModulesOptions;
@@ -50,6 +50,8 @@ declare interface PluginOptions {
5050

5151
declare function CssModulesPlugin(options?: PluginOptions): Plugin;
5252

53-
declare namespace CssModulesPlugin {}
53+
declare namespace CssModulesPlugin {
54+
export type Options = PluginOptions;
55+
}
5456

5557
export = CssModulesPlugin;

index.js

Lines changed: 10 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -1,258 +1,22 @@
1-
const path = require('path');
2-
const { createHash } = require('crypto');
3-
const fse = require('fs-extra');
4-
const fs = require('fs');
5-
const postcss = require('postcss');
6-
const cssModules = require('postcss-modules');
7-
const util = require('util');
8-
const tmp = require('tmp');
9-
const hash = createHash('sha256');
10-
const readFile = util.promisify(fse.readFile);
11-
const writeFile = util.promisify(fse.writeFile);
12-
const ensureDir = util.promisify(fse.ensureDir);
13-
const pluginNamespace = 'esbuild-css-modules-plugin-namespace';
14-
const cssHandler = require('@parcel/css');
15-
const camelCase = require('lodash/camelCase');
16-
17-
const getAbsoluteUrl = (resolveDir, url) => {
18-
const pureUrl = url.replace(/\"/g, '').replace(/\'/g, '');
19-
if (path.isAbsolute(pureUrl) || pureUrl.startsWith('http')) {
20-
return pureUrl;
21-
}
22-
return path.resolve(resolveDir, pureUrl);
23-
};
24-
25-
const buildCssModulesJS2 = async (cssFullPath) => {
26-
const resolveDir = path.dirname(cssFullPath);
27-
const classPrefix = path.basename(cssFullPath, path.extname(cssFullPath)).replace(/\./g, '-') + '__';
28-
29-
/**
30-
* @type {import('@parcel/css').BundleOptions}
31-
*/
32-
const bundleConfig = {
33-
filename: cssFullPath,
34-
minify: false,
35-
sourceMap: true,
36-
cssModules: true,
37-
analyzeDependencies: true
38-
};
39-
const { code, exports = {}, map, dependencies = [] } = cssHandler.bundle(bundleConfig);
40-
41-
let finalCssContent = code.toString('utf-8');
42-
43-
const cssModulesJSON = {};
44-
Object.keys(exports).forEach((originClass) => {
45-
const patchedClass = exports[originClass].name;
46-
cssModulesJSON[camelCase(originClass)] = classPrefix + patchedClass;
47-
finalCssContent = finalCssContent.replace(
48-
new RegExp(`\\.${patchedClass}`, 'g'),
49-
'.' + classPrefix + patchedClass
50-
)
51-
});
52-
const classNames = JSON.stringify(cssModulesJSON, null, 2);
53-
54-
const urls = dependencies.filter((d) => d.type === 'url');
55-
urls.forEach(({ url, placeholder }) => {
56-
finalCssContent = finalCssContent.replace(
57-
new RegExp(`${placeholder}`, 'g'),
58-
getAbsoluteUrl(resolveDir, url)
59-
);
60-
});
61-
62-
const jsContent = `export default ${classNames};`;
63-
64-
if (map) {
65-
finalCssContent += `\n/*# sourceMappingURL=data:application/json;base64,${map.toString(
66-
'base64'
67-
)} */`;
68-
}
69-
return {
70-
jsContent,
71-
cssContent: finalCssContent
72-
};
73-
};
74-
75-
const buildCssModulesJS = async (cssFullPath, options) => {
76-
const {
77-
localsConvention = 'camelCaseOnly',
78-
inject = true,
79-
generateScopedName,
80-
cssModulesOption = {}
81-
} = options;
82-
83-
const css = await readFile(cssFullPath);
84-
85-
let cssModulesJSON = {};
86-
const result = await postcss([
87-
cssModules({
88-
localsConvention,
89-
generateScopedName,
90-
getJSON(cssSourceFile, json) {
91-
cssModulesJSON = { ...json };
92-
return cssModulesJSON;
93-
},
94-
...cssModulesOption
95-
})
96-
]).process(css, {
97-
from: cssFullPath,
98-
map: false
99-
});
100-
101-
const classNames = JSON.stringify(cssModulesJSON);
102-
hash.update(cssFullPath);
103-
const digest = hash.copy().digest('hex');
104-
105-
let injectedCode = '';
106-
if (inject === true) {
107-
injectedCode = `
108-
(function() {
109-
if (!document.getElementById(digest)) {
110-
var el = document.createElement('style');
111-
el.id = digest;
112-
el.textContent = css;
113-
document.head.appendChild(el);
114-
}
115-
})();
116-
`;
117-
} else if (typeof inject === 'function') {
118-
injectedCode = inject(result.css, digest);
119-
}
120-
121-
let jsContent = `
122-
const digest = '${digest}';
123-
const css = \`${result.css}\`;
124-
${injectedCode}
125-
export default ${classNames};
126-
export { css, digest };
127-
`;
128-
129-
return {
130-
jsContent,
131-
cssContent: result.css
132-
};
133-
};
1+
const pluginV1 = require('./lib/v1');
2+
const plugin = require('./lib/plugin');
3+
const { pluginName } = require('./lib/utils');
1344

5+
/**
6+
* @type {(options: import('.').Options) => import('esbuild').Plugin}
7+
*/
1358
const CssModulesPlugin = (options = {}) => {
1369
return {
137-
name: 'esbuild-css-modules-plugin',
10+
name: pluginName,
13811
setup(build) {
139-
const { outdir, bundle, logLevel, watch, target } = build.initialOptions;
12+
const { bundle } = build.initialOptions;
14013
const { v2 } = options;
141-
const rootDir = process.cwd();
142-
const tmpDirPath = tmp.dirSync().name;
143-
const tmpRoot = path.resolve(process.cwd(), outdir, '.esbuild_plugin_css_modules');
144-
145-
const outputLogs = logLevel === 'debug' || logLevel === 'verbose';
146-
14714
const useV2 = v2 && bundle;
14815

14916
if (useV2) {
150-
build.onLoad({ filter: /\.modules?\.css$/ }, async (args) => {
151-
const fullPath = args.path;
152-
const hex = createHash('sha256').update(fullPath).digest('hex');
153-
const tmpDir = path.resolve(tmpRoot, hex.slice(hex.length - 255, hex.length));
154-
155-
const tmpCssFile = path.join(
156-
tmpDir,
157-
fullPath.replace(rootDir, '').replace(/\.modules?\.css$/, '.modules_built.css')
158-
);
159-
160-
fse.ensureDirSync(path.dirname(tmpCssFile));
161-
162-
const { jsContent, cssContent } = await buildCssModulesJS2(fullPath);
163-
fs.writeFileSync(tmpCssFile, `${cssContent}`, { encoding: 'utf-8' });
164-
outputLogs &&
165-
console.log(`[css-modules-plugin] build css file`, tmpCssFile.replace(tmpDir, ''));
166-
167-
const jsFileContent = `import "${tmpCssFile
168-
// fix path issue on Windows: https://github.com/indooorsman/esbuild-css-modules-plugin/issues/12
169-
.split(path.sep)
170-
.join(path.posix.sep)}";\n${jsContent}`;
171-
172-
return {
173-
contents: jsFileContent,
174-
loader: 'js'
175-
};
176-
});
177-
178-
build.onEnd(() => {
179-
if (watch) {
180-
return;
181-
}
182-
outputLogs && console.log('[css-modules-plugin] clean temp files...');
183-
try {
184-
fse.removeSync(tmpRoot);
185-
} catch (error) {}
186-
});
17+
plugin.setup(build, options);
18718
} else {
188-
build.onResolve({ filter: /\.modules?\.css$/, namespace: 'file' }, async (args) => {
189-
const sourceFullPath = path.resolve(args.resolveDir, args.path);
190-
const sourceExt = path.extname(sourceFullPath);
191-
const sourceBaseName = path.basename(sourceFullPath, sourceExt);
192-
const sourceDir = path.dirname(sourceFullPath);
193-
const sourceRelDir = path.relative(path.dirname(rootDir), sourceDir);
194-
const tmpDir = path.resolve(tmpDirPath, sourceRelDir);
195-
await ensureDir(tmpDir);
196-
const tmpFilePath = path.resolve(tmpDir, `${sourceBaseName}.css`);
197-
198-
const { jsContent } = await buildCssModulesJS(sourceFullPath, options);
199-
200-
await writeFile(`${tmpFilePath}.js`, jsContent, { encoding: 'utf-8' });
201-
202-
if (outdir && !bundle) {
203-
const isOutdirAbsolute = path.isAbsolute(outdir);
204-
const absoluteOutdir = isOutdirAbsolute
205-
? outdir
206-
: path.resolve(args.resolveDir, outdir);
207-
const isEntryAbsolute = path.isAbsolute(args.path);
208-
const entryRelDir = isEntryAbsolute
209-
? path.dirname(path.relative(args.resolveDir, args.path))
210-
: path.dirname(args.path);
211-
const targetSubpath =
212-
absoluteOutdir.indexOf(entryRelDir) === -1
213-
? path.join(entryRelDir, `${sourceBaseName}.css.js`)
214-
: `${sourceBaseName}.css.js`;
215-
const target = path.resolve(absoluteOutdir, targetSubpath);
216-
await ensureDir(path.dirname(target));
217-
fse.copyFileSync(`${tmpFilePath}.js`, target);
218-
outputLogs &&
219-
console.log(
220-
'[css-modules-plugin]',
221-
path.relative(rootDir, sourceFullPath),
222-
'=>',
223-
path.relative(rootDir, target)
224-
);
225-
}
226-
if (!bundle) {
227-
return { path: sourceFullPath, namespace: 'file' };
228-
}
229-
return {
230-
path: `${tmpFilePath}.js`,
231-
namespace: pluginNamespace,
232-
pluginData: {
233-
content: jsContent,
234-
resolveArgs: {
235-
path: args.path,
236-
fullPath: sourceFullPath,
237-
importer: args.importer,
238-
namespace: args.namespace,
239-
resolveDir: args.resolveDir,
240-
kind: args.kind
241-
}
242-
}
243-
};
244-
});
245-
246-
build.onLoad({ filter: /\.modules?\.css\.js$/, namespace: pluginNamespace }, (args) => {
247-
const { path: resolvePath, importer, fullPath } = args.pluginData.resolveArgs;
248-
const importerName = path.basename(importer);
249-
outputLogs &&
250-
console.log(
251-
'[css-modules-plugin]',
252-
`${resolvePath} => ${resolvePath}.js => ${importerName}`
253-
);
254-
return { contents: args.pluginData.content, loader: 'js', watchFiles: [fullPath] };
255-
});
19+
pluginV1.setup(build, options);
25620
}
25721
}
25822
};

0 commit comments

Comments
 (0)