Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Clarity Flowers committed Apr 20, 2020
2 parents 23968fd + d64ff10 commit a284817
Show file tree
Hide file tree
Showing 12 changed files with 4,797 additions and 127 deletions.
13 changes: 0 additions & 13 deletions lib/get-import-name.js

This file was deleted.

8 changes: 0 additions & 8 deletions lib/get-regex.js

This file was deleted.

8 changes: 4 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
let React;
let React = undefined;
try {
React = require("react");
} catch {
React = null;
} catch (e) {
// This library works without react!
}
const cx = require("classnames");

/**
* A higher-order component that wraps the given html element
* with the generated classname.
*
* @param {string} element the html element to be styled
* @param {any} element the html element to be styled
* @param {string} styleClass the generated classname of the component
*/
const resplendent = (element, styleClass) => {
Expand Down
17 changes: 0 additions & 17 deletions lib/make-classname.js

This file was deleted.

61 changes: 47 additions & 14 deletions lib/script-loader.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,69 @@
const makeClassName = require("./make-classname");
const loaderUtils = require("loader-utils");
const getImportName = require("./get-import-name");
const getRegex = require("./get-regex");
const { importRegex, getRegex, makeClassName } = require("./util");

/**
* Strip all the CSS out of the js file and add an import for
* itself with resplendence=true
*
* @this {import('./util').LoaderContext}
*
* @param {string} source - The source code of the file that will be transformed
*/
const scriptLoader = function(source) {
const { src } = loaderUtils.getOptions(this) || {};
/** @type string */
const srcDir = (loaderUtils.getOptions(this) || {}).src;

const importName = getImportName(source);
if (!importName) return this.callback(null, source);
const importMatch = source.match(importRegex);
if (!importMatch) {
// If they didn't import resplendence, don't do any processing at all.
this.callback(null, source);
return;
}
const importName = importMatch[1];
const regex = getRegex(importName);

let count = 0;
let output = source.replace(regex, (_m, parens, args) => {

let hasComponents = false;
let hasStyles = false;
let output = source.replace(regex, (_all, parens, args, content) => {
hasStyles = true;
// Wrap the style code in comments, making sure to filter out any multiline
// comments that might already be in there to avoid issues with nesting.
const comment = `/*${content.replace("/*", "~*").replace("*/", "*~")}*/`;
if (parens) {
const className = makeClassName(this.resourcePath, ++count, src);
const className = makeClassName(this.resourcePath, ++count, srcDir);
if (args && args.trim()) {
return `${importName}(${args}, "${className}")`;
// This is a styled component --- rx("div")``
hasComponents = true;
return `${importName}(${args}, "${className}")${comment}`;
} else {
// This is a styled classname --- rx()``
return `"${className}"${comment}`;
}
return `"${className}"`;
} else {
// This is a bare style --- rx``
return comment;
}
return "";
});

if (count) {
output = `import "${this.resourcePath}?resplendence=true";\n${output}`;
if (!hasStyles) {
// Whoops, even though they imported rx, they didn't actually use it!
this.callback(null, source);
return;
}

return this.callback(null, output);
output = output.replace(importRegex, all => {
// The file imports itself, but with the query param resplendence=true so
// that webpack can know to interpret it as a style instead of a script.
let result = `import "${this.resourcePath}?resplendence=true";`;
if (hasComponents) {
result += all;
}
return result;
});

this.callback(null, output);
};

module.exports = scriptLoader;
56 changes: 41 additions & 15 deletions lib/style-loader.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,61 @@
const makeClassName = require("./make-classname");
const loaderUtils = require("loader-utils");
const getRegex = require("./get-regex");
const getImportName = require("./get-import-name");
const { importRegex, getRegex, makeClassName } = require("./util");

// Big strings that we are hoping don't show up in the user's code anywhere
// Because we'll be doing find/replaces on them
const DELETE_START = "!RESPLENDENCE CODE TO DELETE START!";
const DELETE_END = "!RESPLENDENCE CODE TO DELETE END!";

/**
* Transform a js file with resplendence=true into a CSS file.
*
* @this {import('./util').LoaderContext}
*
* @param {string} source
*/
const styleLoader = function(source) {
const { src } = loaderUtils.getOptions(this) || {};

if (!this.resourceQuery.includes("resplendence=true")) {
return this.callback(null, source);
this.callback(null, source);
return;
}

const importName = getImportName(source);
if (!importName) return this.callback(null, source);
const importMatch = source.match(importRegex);
if (!importMatch) {
this.callback(null, source);
return;
}
const importName = importMatch[1];
const regex = getRegex(importName);

let result = "";
let match;
let count = 0;
while ((match = regex.exec(source))) {
if (match[1]) {
// Wrap everything in a flag that this code should be deleted...
let result = DELETE_START + source + DELETE_END;

result = result.replace(regex, (_all, parens, _args, content) => {
let code = content;
if (parens) {
const className = makeClassName(this.resourcePath, ++count, src);
result += `.${className} {\n${match[3]}\n}\n`;
} else {
result += match[3] + "\n";
code = `.${className} {${content}}`;
}
}
// ... then carve out exceptions for the css ...
return DELETE_END + code + DELETE_START;
});

// ... then strip all of the code marked for deletion,
// leaving only the newlines, to preserve line numbers ...
result = result.replace(
new RegExp(`${DELETE_START}([\\s\\S]*?)${DELETE_END}`, "g"),
(_all, code) => {
return code.replace(/[\S \t]+/g, "");
}
);

// ... and finally, trim trailing whitespace
result = result.replace(/\s+$/, "");

return this.callback(null, result);
this.callback(null, result);
};

module.exports = styleLoader;
49 changes: 49 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const path = require("path");

/**
* Add a source file, and optionally a map
*
* @callback loaderCallback
* @param {?Error} error
* @param {string} code
* @param {Object=} sourceMap
* @param {any=} any
*/

/**
* The context passed into a loader
*
* @typedef {Object} LoaderContext
* @property {string} resourcePath
* @property {loaderCallback} callback
* @property {string|Object|null} query
* @property {boolean} sourceMap
* @property {string} resourceQuery
*/

/**
* Produce a classname from the path relative to the configured source directory.
* @param {string} filepath the file's path
* @param {number} index the current count of classnames added for this file
* @param {string} src the configured source directory
*/
exports.makeClassName = function(filepath, index, src) {
const name = path
.relative(src, filepath)
.replace(/[/\\]/g, "-")
.replace(/\..*$/, "");
return `rx-${name}-${index}`;
};

/**
* Gets the regex that looks for resplendent styles.
* @param {string} importName
*/
exports.getRegex = importName =>
new RegExp(importName + "(\\((.*?)?\\))?`((.|[\\s\\S])*?)`", "g");

/**
* regex to fetch the line where resplendence is imported. If you don't import
* resplendence, then the file won't be processed at all.
*/
exports.importRegex = /import +(\S+?) +from +['"]resplendence['"] *;?/;
Loading

0 comments on commit a284817

Please sign in to comment.