diff --git a/src/FileSystemLoader.js b/src/FileSystemLoader.js index 2b17c7e..fe1f307 100644 --- a/src/FileSystemLoader.js +++ b/src/FileSystemLoader.js @@ -1,10 +1,10 @@ // Initially copied from https://github.com/css-modules/css-modules-loader-core import postcss from "postcss"; -import fs from "fs"; import path from "path"; import Parser from "./Parser"; +import { getFileSystem } from "./fs"; class Core { constructor(plugins) { @@ -62,6 +62,7 @@ export default class FileSystemLoader { this.importNr = 0; this.core = new Core(plugins); this.tokensByFile = {}; + this.fs = getFileSystem(); } async fetch(_newPath, relativeTo, _trace) { @@ -97,7 +98,7 @@ export default class FileSystemLoader { if (tokens) return tokens; return new Promise((resolve, reject) => { - fs.readFile(fileRelativePath, "utf-8", async (err, source) => { + this.fs.readFile(fileRelativePath, "utf-8", async (err, source) => { if (err) reject(err); const { injectableSource, exportTokens } = await this.core.load( diff --git a/src/Parser.js b/src/Parser.js index 0c959e5..569f2b3 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -1,4 +1,4 @@ -// Copied from https://github.com/css-modules/css-modules-loader-core +// Initially copied from https://github.com/css-modules/css-modules-loader-core const importRegexp = /^:import\((.+)\)$/; import { replaceSymbols } from "icss-utils"; @@ -16,10 +16,10 @@ export default class Parser { const parser = this; return { postcssPlugin: "css-modules-parser", - OnceExit(css) { - return Promise.all(parser.fetchAllImports(css)) - .then(() => parser.linkImportedSymbols(css)) - .then(() => parser.extractExports(css)); + async OnceExit(css) { + await Promise.all(parser.fetchAllImports(css)); + parser.linkImportedSymbols(css); + return parser.extractExports(css); }, }; } @@ -56,19 +56,21 @@ export default class Parser { exportNode.remove(); } - fetchImport(importNode, relativeTo, depNr) { - let file = importNode.selector.match(importRegexp)[1], - depTrace = this.trace + String.fromCharCode(depNr); - return this.pathFetcher(file, relativeTo, depTrace).then( - (exports) => { - importNode.each((decl) => { - if (decl.type == "decl") { - this.translations[decl.prop] = exports[decl.value]; - } - }); - importNode.remove(); - }, - (err) => console.log(err) - ); + async fetchImport(importNode, relativeTo, depNr) { + const file = importNode.selector.match(importRegexp)[1]; + const depTrace = this.trace + String.fromCharCode(depNr); + + const exports = await this.pathFetcher(file, relativeTo, depTrace); + + try { + importNode.each((decl) => { + if (decl.type == "decl") { + this.translations[decl.prop] = exports[decl.value]; + } + }); + importNode.remove(); + } catch (err) { + console.log(err); + } } } diff --git a/src/behaviours.js b/src/behaviours.js deleted file mode 100644 index d48fdd9..0000000 --- a/src/behaviours.js +++ /dev/null @@ -1,28 +0,0 @@ -import localByDefault from "postcss-modules-local-by-default"; -import extractImports from "postcss-modules-extract-imports"; -import modulesScope from "postcss-modules-scope"; -import values from "postcss-modules-values"; - -export const behaviours = { - LOCAL: "local", - GLOBAL: "global", -}; - -export function getDefaultPlugins({ behaviour, generateScopedName, exportGlobals }) { - const scope = modulesScope({ generateScopedName, exportGlobals }); - - const plugins = { - [behaviours.LOCAL]: [values, localByDefault({ mode: "local" }), extractImports, scope], - [behaviours.GLOBAL]: [values, localByDefault({ mode: "global" }), extractImports, scope], - }; - - return plugins[behaviour]; -} - -export function isValidBehaviour(behaviour) { - return ( - Object.keys(behaviours) - .map((key) => behaviours[key]) - .indexOf(behaviour) > -1 - ); -} diff --git a/src/fs.js b/src/fs.js new file mode 100644 index 0000000..42c0040 --- /dev/null +++ b/src/fs.js @@ -0,0 +1,18 @@ +let fileSystem = { + readFile: () => { + throw Error("readFile not implemented"); + }, + + writeFile: () => { + throw Error("writeFile not implemented"); + }, +}; + +export function setFileSystem(fs) { + fileSystem.readFile = fs.readFile + fileSystem.writeFile = fs.writeFile +} + +export function getFileSystem() { + return fileSystem; +} diff --git a/src/generateScopedName.js b/src/generateScopedName.js deleted file mode 100644 index 7a38489..0000000 --- a/src/generateScopedName.js +++ /dev/null @@ -1,9 +0,0 @@ -import stringHash from "string-hash"; - -export default function generateScopedName(name, filename, css) { - const i = css.indexOf(`.${name}`); - const lineNumber = css.substr(0, i).split(/[\r\n]/).length; - const hash = stringHash(css).toString(36).substr(0, 5); - - return `_${name}_${hash}_${lineNumber}`; -} diff --git a/src/index.js b/src/index.js index d0acb9c..3b4b056 100644 --- a/src/index.js +++ b/src/index.js @@ -1,34 +1,23 @@ import postcss from "postcss"; import camelCase from "lodash.camelcase"; -import genericNames from "generic-names"; import unquote from "./unquote"; +import { readFile, writeFile } from "fs"; +import { setFileSystem } from "./fs"; import Parser from "./Parser"; import FileSystemLoader from "./FileSystemLoader"; -import generateScopedName from "./generateScopedName"; import saveJSON from "./saveJSON"; -import { getDefaultPlugins, isValidBehaviour, behaviours } from "./behaviours"; +import { + getDefaultPlugins, + getDefaultScopeBehaviour, + behaviours, + getScopedNameGenerator, +} from "./scoping"; const PLUGIN_NAME = "postcss-modules"; -function getDefaultScopeBehaviour(opts) { - if (opts.scopeBehaviour && isValidBehaviour(opts.scopeBehaviour)) { - return opts.scopeBehaviour; - } - - return behaviours.LOCAL; -} - -function getScopedNameGenerator(opts) { - const scopedNameGenerator = opts.generateScopedName || generateScopedName; - - if (typeof scopedNameGenerator === "function") return scopedNameGenerator; - return genericNames(scopedNameGenerator, { - context: process.cwd(), - hashPrefix: opts.hashPrefix, - }); -} +setFileSystem({ readFile, writeFile }); function getLoader(opts, plugins) { const root = typeof opts.root === "undefined" ? "/" : opts.root; @@ -44,8 +33,8 @@ function isGlobalModule(globalModules, inputFile) { function getDefaultPluginsList(opts, inputFile) { const globalModulesList = opts.globalModulePaths || null; const exportGlobals = opts.exportGlobals || false; - const defaultBehaviour = getDefaultScopeBehaviour(opts); - const generateScopedName = getScopedNameGenerator(opts); + const defaultBehaviour = getDefaultScopeBehaviour(opts.scopeBehaviour); + const generateScopedName = getScopedNameGenerator(opts.generateScopedName, opts.hashPrefix); if (globalModulesList && isGlobalModule(globalModulesList, inputFile)) { return getDefaultPlugins({ diff --git a/src/saveJSON.js b/src/saveJSON.js index 950cc8f..12d76ad 100644 --- a/src/saveJSON.js +++ b/src/saveJSON.js @@ -1,7 +1,8 @@ -import { writeFile } from "fs"; +import { getFileSystem } from "./fs"; export default function saveJSON(cssFile, json) { return new Promise((resolve, reject) => { + const { writeFile } = getFileSystem(); writeFile(`${cssFile}.json`, JSON.stringify(json), (e) => (e ? reject(e) : resolve(json))); }); } diff --git a/src/scoping.js b/src/scoping.js new file mode 100644 index 0000000..9d53276 --- /dev/null +++ b/src/scoping.js @@ -0,0 +1,55 @@ +import extractImports from "postcss-modules-extract-imports"; +import genericNames from "generic-names"; +import localByDefault from "postcss-modules-local-by-default"; +import modulesScope from "postcss-modules-scope"; +import stringHash from "string-hash"; +import values from "postcss-modules-values"; + +export const behaviours = { + LOCAL: "local", + GLOBAL: "global", +}; + +export function getDefaultPlugins({ behaviour, generateScopedName, exportGlobals }) { + const scope = modulesScope({ generateScopedName, exportGlobals }); + + const plugins = { + [behaviours.LOCAL]: [values, localByDefault({ mode: "local" }), extractImports, scope], + [behaviours.GLOBAL]: [values, localByDefault({ mode: "global" }), extractImports, scope], + }; + + return plugins[behaviour]; +} + +function isValidBehaviour(behaviour) { + return ( + Object.keys(behaviours) + .map((key) => behaviours[key]) + .indexOf(behaviour) > -1 + ); +} + +export function getDefaultScopeBehaviour(scopeBehaviour) { + return scopeBehaviour && isValidBehaviour(scopeBehaviour) ? scopeBehaviour : behaviours.LOCAL; +} + +function generateScopedNameDefault(name, filename, css) { + const i = css.indexOf(`.${name}`); + const lineNumber = css.substr(0, i).split(/[\r\n]/).length; + const hash = stringHash(css).toString(36).substr(0, 5); + + return `_${name}_${hash}_${lineNumber}`; +} + +export function getScopedNameGenerator(generateScopedName, hashPrefix) { + const scopedNameGenerator = generateScopedName || generateScopedNameDefault; + + if (typeof scopedNameGenerator === "function") { + return scopedNameGenerator; + } + + return genericNames(scopedNameGenerator, { + context: process.cwd(), + hashPrefix: hashPrefix, + }); +} diff --git a/test/test.js b/test/test.js index 39851a1..37b334a 100644 --- a/test/test.js +++ b/test/test.js @@ -3,7 +3,7 @@ import autoprefixer from "autoprefixer"; import fs from "fs"; import path from "path"; import plugin from "../src"; -import { behaviours } from "../src/behaviours"; +import { behaviours } from "../src/scoping"; const fixturesPath = path.resolve(__dirname, "./fixtures");