esbuild-style-plugin
Version:
Another esbuild plugin for your styling with CSS,SASS,LESS,STYLUS
211 lines (208 loc) • 8.21 kB
JavaScript
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
// src/utils.ts
import { TextDecoder } from "util";
import path from "path";
import fs from "fs";
import { globSync } from "glob";
var getModule, renderStylus, renderStyle, importPostcssConfigFile, getPostCSSWatchFiles;
var init_utils = __esm({
"src/utils.ts"() {
getModule = async (moduleName, checkFunc) => {
try {
const module = await import(moduleName);
if (typeof module[checkFunc] === `function`)
return module;
if (typeof module.default[checkFunc] === `function`)
return module.default;
throw new Error(`Func '${checkFunc}' not found for module '${moduleName}'`);
} catch (e) {
if (e.code === "MODULE_NOT_FOUND") {
throw new Error(`Missing module. Please install '${moduleName}' package.`);
} else {
throw e;
}
}
};
renderStylus = async (css, options) => {
const stylus = await getModule("stylus", "render");
return new Promise((resolve, reject) => {
stylus.render(css, options, (err, css2) => {
if (err)
reject(err);
resolve(css2);
});
});
};
renderStyle = async (filePath, options = {}) => {
const { ext } = path.parse(filePath);
if (ext === ".css") {
return (await fs.promises.readFile(filePath)).toString();
}
if (ext === ".sass" || ext === ".scss") {
const sassOptions = options.sassOptions || {};
const sass = await getModule("sass", "renderSync");
return sass.renderSync({ ...sassOptions, file: filePath }).css.toString();
}
if (ext === ".styl") {
const stylusOptions = options.stylusOptions || {};
const source = await fs.promises.readFile(filePath);
return await renderStylus(new TextDecoder().decode(source), { ...stylusOptions, filename: filePath });
}
if (ext === ".less") {
const lestOptions = options.lessOptions || {};
const source = await fs.promises.readFile(filePath);
const less = await getModule("less", "render");
return (await less.render(new TextDecoder().decode(source), { ...lestOptions, filename: filePath })).css;
}
throw new Error(`Can't render this style '${ext}'.`);
};
importPostcssConfigFile = async (configFilePath) => {
let _configFilePath = configFilePath === true ? path.resolve(process.cwd(), "postcss.config.js") : configFilePath;
try {
const imported = await import(_configFilePath);
if (!imported.default)
throw new Error(`Missing default import .`);
const config = imported.default;
if (!config.plugins)
throw new Error(`Missing plugins [array].`);
return config;
} catch (err) {
console.error(err);
throw new Error(`PostCSS config file at ${_configFilePath} can't load.`);
}
};
getPostCSSWatchFiles = (result) => {
let watchFiles = [];
const { messages } = result;
for (const message of messages) {
const { type } = message;
if (type === "dependency") {
watchFiles.push(message.file);
} else if (type === "dir-dependency") {
if (!message.dir)
continue;
let globString = `**/*`;
if (message.glob && message.glob !== "")
globString = message.glob;
const globPath = path.join(message.dir, globString);
const files = globSync(globPath);
watchFiles = [...watchFiles, ...files];
}
}
return watchFiles;
};
}
});
// src/index.ts
import { createHash } from "crypto";
import path2 from "path";
import postcss from "postcss";
import cssModules from "postcss-modules";
var require_src = __commonJS({
"src/index.ts"(exports, module) {
init_utils();
var LOAD_TEMP_NAMESPACE = "temp_stylePlugin";
var LOAD_STYLE_NAMESPACE = "stylePlugin";
var SKIP_RESOLVE = "esbuild-style-plugin-skipResolve";
var styleFilter = /.\.(css|sass|scss|less|styl)$/;
var handleCSSModules = (mapping, cssModulesOptions) => {
const _getJSON = cssModulesOptions.getJSON;
return cssModules({
...cssModulesOptions,
getJSON: (cssFilename, json, outputFilename) => {
if (typeof _getJSON === "function")
_getJSON(cssFilename, json, outputFilename);
mapping.data = JSON.stringify(json, null, 2);
}
});
};
var onTempStyleResolve = async (build, args) => {
const { importer, pluginData, resolveDir } = args;
return {
path: path2.relative(build.initialOptions.absWorkingDir || "", importer),
namespace: LOAD_TEMP_NAMESPACE,
pluginData: { contents: pluginData, resolveDir }
};
};
var onStyleResolve = async (build, args) => {
const { namespace } = args;
if (args.pluginData === SKIP_RESOLVE || namespace === LOAD_STYLE_NAMESPACE || namespace === LOAD_TEMP_NAMESPACE)
return;
const result = await build.resolve(args.path, { resolveDir: args.resolveDir, pluginData: SKIP_RESOLVE, kind: args.kind });
if (result.errors.length > 0) {
return { errors: result.errors };
}
const fullPath = result.path;
if (!styleFilter.test(fullPath))
return;
return {
path: fullPath,
namespace: LOAD_STYLE_NAMESPACE,
watchFiles: [fullPath]
};
};
var onTempLoad = async (args) => {
const { pluginData } = args;
return {
resolveDir: pluginData.resolveDir,
contents: pluginData.contents,
loader: "css"
};
};
var onStyleLoad = (options) => async (args) => {
const extract = options.extract === void 0 ? true : options.extract;
const cssModulesMatch = options.cssModulesMatch || /\.module\./;
const isCSSModule = args.path.match(cssModulesMatch);
const cssModulesOptions = options.cssModulesOptions || {};
const renderOptions = options.renderOptions;
let css = await renderStyle(args.path, renderOptions);
let watchFiles = [];
let mapping = { data: {} };
let { plugins = [], ...processOptions } = options.postcss || {};
let injectMapping = false;
let contents = "";
if (isCSSModule) {
plugins = [handleCSSModules(mapping, cssModulesOptions), ...plugins];
injectMapping = true;
}
if (plugins.length > 0) {
const result = await postcss(plugins).process(css, { ...processOptions, from: args.path });
css = result.css;
watchFiles = [...watchFiles, ...getPostCSSWatchFiles(result)];
if (injectMapping)
contents += `export default ${mapping.data};`;
}
if (extract) {
contents += `import ${JSON.stringify("ni:sha-256;" + createHash("sha256").update(css).digest("base64url"))};`;
}
return {
watchFiles,
resolveDir: path2.dirname(args.path),
// Keep resolveDir for onTempLoad anything resolve inside temp file must be resolve using source dir
contents,
pluginData: css
};
};
var stylePlugin = (options = {}) => ({
name: "esbuild-style-plugin",
setup: async (build) => {
if (options.postcssConfigFile) {
console.log(`Using postcss config file.`);
options.postcss = await importPostcssConfigFile(options.postcssConfigFile);
}
build.onResolve({ filter: styleFilter }, onStyleResolve.bind(null, build));
build.onResolve({ filter: /^ni:/, namespace: LOAD_STYLE_NAMESPACE }, onTempStyleResolve.bind(null, build));
build.onLoad({ filter: /.*/, namespace: LOAD_TEMP_NAMESPACE }, onTempLoad);
build.onLoad({ filter: /.*/, namespace: LOAD_STYLE_NAMESPACE }, onStyleLoad(options));
}
});
module.exports = stylePlugin;
}
});
export default require_src();