Skip to content

Commit

Permalink
Extract pluginFactory and localConvention into separate modules
Browse files Browse the repository at this point in the history
  • Loading branch information
madyankin committed Nov 4, 2022
1 parent e87c4ab commit a453783
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 138 deletions.
140 changes: 2 additions & 138 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,144 +1,8 @@
import postcss from "postcss";
import camelCase from "lodash.camelcase";
import unquote from "./unquote";
import { readFile, writeFile } from "fs";
import { setFileSystem } from "./fs";

import Parser from "./Parser";
import FileSystemLoader from "./FileSystemLoader";

import saveJSON from "./saveJSON";
import {
getDefaultPlugins,
getDefaultScopeBehaviour,
behaviours,
getScopedNameGenerator,
} from "./scoping";

const PLUGIN_NAME = "postcss-modules";
import { makePlugin } from "./pluginFactory";

setFileSystem({ readFile, writeFile });

function getLoader(opts, plugins) {
const root = typeof opts.root === "undefined" ? "/" : opts.root;
return typeof opts.Loader === "function"
? new opts.Loader(root, plugins, opts.resolve)
: new FileSystemLoader(root, plugins, opts.resolve);
}

function isGlobalModule(globalModules, inputFile) {
return globalModules.some((regex) => inputFile.match(regex));
}

function getDefaultPluginsList(opts, inputFile) {
const globalModulesList = opts.globalModulePaths || null;
const exportGlobals = opts.exportGlobals || false;
const defaultBehaviour = getDefaultScopeBehaviour(opts.scopeBehaviour);
const generateScopedName = getScopedNameGenerator(opts.generateScopedName, opts.hashPrefix);

if (globalModulesList && isGlobalModule(globalModulesList, inputFile)) {
return getDefaultPlugins({
behaviour: behaviours.GLOBAL,
generateScopedName,
exportGlobals,
});
}

return getDefaultPlugins({
behaviour: defaultBehaviour,
generateScopedName,
exportGlobals,
});
}

function isOurPlugin(plugin) {
return plugin.postcssPlugin === PLUGIN_NAME;
}

function dashesCamelCase(string) {
return string.replace(/-+(\w)/g, (_, firstLetter) => firstLetter.toUpperCase());
}

module.exports = (opts = {}) => {
return {
postcssPlugin: PLUGIN_NAME,
async OnceExit(css, { result }) {
const getJSON = opts.getJSON || saveJSON;
const inputFile = css.source.input.file;
const pluginList = getDefaultPluginsList(opts, inputFile);
const resultPluginIndex = result.processor.plugins.findIndex((plugin) =>
isOurPlugin(plugin)
);
if (resultPluginIndex === -1) {
throw new Error("Plugin missing from options.");
}

const earlierPlugins = result.processor.plugins.slice(0, resultPluginIndex);
const loaderPlugins = [...earlierPlugins, ...pluginList];
const loader = getLoader(opts, loaderPlugins);

const fetcher = async (file, relativeTo, depTrace) => {
const unquoteFile = unquote(file);

return loader.fetch.call(loader, unquoteFile, relativeTo, depTrace);
};
const parser = new Parser(fetcher);

await postcss([...pluginList, parser.plugin()]).process(css, {
from: inputFile,
});

const out = loader.finalSource;
if (out) css.prepend(out);

if (opts.localsConvention) {
const isFunc = typeof opts.localsConvention === "function";

parser.exportTokens = Object.entries(parser.exportTokens).reduce(
(tokens, [className, value]) => {
if (isFunc) {
const convention = opts.localsConvention(className, value, inputFile);
tokens[convention] = value;

return tokens;
}

switch (opts.localsConvention) {
case "camelCase":
tokens[className] = value;
tokens[camelCase(className)] = value;
break;

case "camelCaseOnly":
tokens[camelCase(className)] = value;
break;

case "dashes":
tokens[className] = value;
tokens[dashesCamelCase(className)] = value;
break;

case "dashesOnly":
tokens[dashesCamelCase(className)] = value;
break;
}

return tokens;
},
{}
);
}

result.messages.push({
type: "export",
plugin: "postcss-modules",
exportTokens: parser.exportTokens,
});

// getJSON may return a promise
return getJSON(css.source.input.file, parser.exportTokens, result.opts.to);
},
};
};

module.exports = (opts = {}) => makePlugin(opts);
module.exports.postcss = true;
40 changes: 40 additions & 0 deletions src/localsConvention.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import camelCase from "lodash.camelcase";

function dashesCamelCase(string) {
return string.replace(/-+(\w)/g, (_, firstLetter) => firstLetter.toUpperCase());
}

export function makeLocalsConventionReducer(localsConvention, inputFile) {
const isFunc = typeof localsConvention === "function";

return (tokens, [className, value]) => {
if (isFunc) {
const convention = localsConvention(className, value, inputFile);
tokens[convention] = value;

return tokens;
}

switch (localsConvention) {
case "camelCase":
tokens[className] = value;
tokens[camelCase(className)] = value;
break;

case "camelCaseOnly":
tokens[camelCase(className)] = value;
break;

case "dashes":
tokens[className] = value;
tokens[dashesCamelCase(className)] = value;
break;

case "dashesOnly":
tokens[dashesCamelCase(className)] = value;
break;
}

return tokens;
};
}
99 changes: 99 additions & 0 deletions src/pluginFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import postcss from "postcss";
import unquote from "./unquote";
import Parser from "./Parser";
import saveJSON from "./saveJSON";
import { makeLocalsConventionReducer } from "./localsConvention";
import FileSystemLoader from "./FileSystemLoader";
import {
getDefaultPlugins,
getDefaultScopeBehaviour,
behaviours,
getScopedNameGenerator,
} from "./scoping";

const PLUGIN_NAME = "postcss-modules";

function isGlobalModule(globalModules, inputFile) {
return globalModules.some((regex) => inputFile.match(regex));
}

function getDefaultPluginsList(opts, inputFile) {
const globalModulesList = opts.globalModulePaths || null;
const exportGlobals = opts.exportGlobals || false;
const defaultBehaviour = getDefaultScopeBehaviour(opts.scopeBehaviour);
const generateScopedName = getScopedNameGenerator(opts.generateScopedName, opts.hashPrefix);

if (globalModulesList && isGlobalModule(globalModulesList, inputFile)) {
return getDefaultPlugins({
behaviour: behaviours.GLOBAL,
generateScopedName,
exportGlobals,
});
}

return getDefaultPlugins({
behaviour: defaultBehaviour,
generateScopedName,
exportGlobals,
});
}

function getLoader(opts, plugins) {
const root = typeof opts.root === "undefined" ? "/" : opts.root;

return typeof opts.Loader === "function"
? new opts.Loader(root, plugins, opts.resolve)
: new FileSystemLoader(root, plugins, opts.resolve);
}

function isOurPlugin(plugin) {
return plugin.postcssPlugin === PLUGIN_NAME;
}

export function makePlugin(opts) {
return {
postcssPlugin: PLUGIN_NAME,
async OnceExit(css, { result }) {
const getJSON = opts.getJSON || saveJSON;
const inputFile = css.source.input.file;
const pluginList = getDefaultPluginsList(opts, inputFile);
const resultPluginIndex = result.processor.plugins.findIndex((plugin) =>
isOurPlugin(plugin)
);
if (resultPluginIndex === -1) {
throw new Error("Plugin missing from options.");
}

const earlierPlugins = result.processor.plugins.slice(0, resultPluginIndex);
const loaderPlugins = [...earlierPlugins, ...pluginList];
const loader = getLoader(opts, loaderPlugins);

const fetcher = async (file, relativeTo, depTrace) => {
const unquoteFile = unquote(file);
return loader.fetch.call(loader, unquoteFile, relativeTo, depTrace);
};
const parser = new Parser(fetcher);

await postcss([...pluginList, parser.plugin()]).process(css, {
from: inputFile,
});

const out = loader.finalSource;
if (out) css.prepend(out);

if (opts.localsConvention) {
const reducer = makeLocalsConventionReducer(opts.localsConvention, inputFile);
parser.exportTokens = Object.entries(parser.exportTokens).reduce(reducer, {});
}

result.messages.push({
type: "export",
plugin: "postcss-modules",
exportTokens: parser.exportTokens,
});

// getJSON may return a promise
return getJSON(css.source.input.file, parser.exportTokens, result.opts.to);
},
};
}

0 comments on commit a453783

Please sign in to comment.