diff --git a/.gitignore b/.gitignore index 9b3dec1..1435ffb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ build yarn-error.log test/dist package-lock.json -dist test/dist \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dist/index.esm.js b/dist/index.esm.js new file mode 100644 index 0000000..082973f --- /dev/null +++ b/dist/index.esm.js @@ -0,0 +1,180 @@ +import { + ensureDir, + readFile, + readdirSync, + statSync, + writeFile +} from "fs-extra"; +import {TextDecoder} from "util"; +import path from "path"; +import tmp from "tmp"; +import postcss from "postcss"; +import postcssModules from "postcss-modules"; +import less from "less"; +import stylus from "stylus"; +import resolveFile from "resolve-file"; +const defaultOptions = { + plugins: [], + modules: true, + rootDir: process.cwd(), + sassOptions: {}, + lessOptions: {}, + stylusOptions: {}, + fileIsModule: null +}; +const postCSSPlugin = ({ + plugins = [], + modules = true, + rootDir = process.cwd(), + sassOptions = {}, + lessOptions = {}, + stylusOptions = {}, + fileIsModule +} = defaultOptions) => ({ + name: "postcss2", + setup(build) { + const tmpDirPath = tmp.dirSync().name, modulesMap = []; + const modulesPlugin = postcssModules({ + generateScopedName: "[name]__[local]___[hash:base64:5]", + ...typeof modules !== "boolean" ? modules : {}, + getJSON(filepath, json, outpath) { + const mapIndex = modulesMap.findIndex((m) => m.path === filepath); + if (mapIndex !== -1) { + modulesMap[mapIndex].map = json; + } else { + modulesMap.push({ + path: filepath, + map: json + }); + } + if (typeof modules !== "boolean" && typeof modules.getJSON === "function") + return modules.getJSON(filepath, json, outpath); + } + }); + build.onResolve({filter: /.\.(css|sass|scss|less|styl)$/}, async (args) => { + if (args.namespace !== "file" && args.namespace !== "") + return; + let sourceFullPath = resolveFile(args.path); + if (!sourceFullPath) + sourceFullPath = path.resolve(args.resolveDir, args.path); + const sourceExt = path.extname(sourceFullPath); + const sourceBaseName = path.basename(sourceFullPath, sourceExt); + const isModule = fileIsModule ? fileIsModule(sourceFullPath) : sourceBaseName.match(/\.module$/); + const sourceDir = path.dirname(sourceFullPath); + const watchFiles = [sourceFullPath]; + let tmpFilePath; + if (args.kind === "entry-point") { + const sourceRelDir = path.relative(path.dirname(rootDir), path.dirname(sourceFullPath)); + tmpFilePath = path.resolve(tmpDirPath, sourceRelDir, `${sourceBaseName}.css`); + await ensureDir(path.dirname(tmpFilePath)); + } else { + const uniqueTmpDir = path.resolve(tmpDirPath, uniqueId()); + tmpFilePath = path.resolve(uniqueTmpDir, `${sourceBaseName}.css`); + } + await ensureDir(path.dirname(tmpFilePath)); + const fileContent = await readFile(sourceFullPath); + let css = sourceExt === ".css" ? fileContent : ""; + if (sourceExt === ".sass" || sourceExt === ".scss") { + const sassResult = await renderSass({ + ...sassOptions, + file: sourceFullPath + }); + css = sassResult.css.toString(); + watchFiles.push(...sassResult.stats.includedFiles); + } + if (sourceExt === ".styl") + css = await renderStylus(new TextDecoder().decode(fileContent), { + ...stylusOptions, + filename: sourceFullPath + }); + if (sourceExt === ".less") + css = (await less.render(new TextDecoder().decode(fileContent), { + ...lessOptions, + filename: sourceFullPath, + rootpath: path.dirname(args.path) + })).css; + const result = await postcss(isModule ? [modulesPlugin, ...plugins] : plugins).process(css, { + from: sourceFullPath, + to: tmpFilePath + }); + watchFiles.push(...getPostCssDependencies(result.messages)); + await writeFile(tmpFilePath, result.css); + return { + namespace: isModule ? "postcss-module" : "file", + path: tmpFilePath, + watchFiles, + pluginData: { + originalPath: sourceFullPath + } + }; + }); + build.onLoad({filter: /.*/, namespace: "postcss-module"}, async (args) => { + const mod = modulesMap.find(({path: path2}) => path2 === args?.pluginData?.originalPath), resolveDir = path.dirname(args.path); + return { + resolveDir, + contents: `import ${JSON.stringify(args.path)}; +export default ${JSON.stringify(mod && mod.map ? mod.map : {})};` + }; + }); + } +}); +function renderSass(options) { + return new Promise((resolve, reject) => { + getSassImpl().render(options, (e, res) => { + if (e) + reject(e); + else + resolve(res); + }); + }); +} +function renderStylus(str, options) { + return new Promise((resolve, reject) => { + stylus.render(str, options, (e, res) => { + if (e) + reject(e); + else + resolve(res); + }); + }); +} +function getSassImpl() { + let impl = "sass"; + try { + require.resolve("sass"); + } catch { + try { + require.resolve("node-sass"); + impl = "node-sass"; + } catch { + throw new Error('Please install "sass" or "node-sass" package'); + } + } + return require(impl); +} +function getFilesRecursive(directory) { + return readdirSync(directory).reduce((files, file) => { + const name = path.join(directory, file); + return statSync(name).isDirectory() ? [...files, ...getFilesRecursive(name)] : [...files, name]; + }, []); +} +let idCounter = 0; +function uniqueId() { + return Date.now().toString(16) + (idCounter++).toString(16); +} +function getPostCssDependencies(messages) { + let dependencies = []; + for (const message of messages) { + if (message.type == "dir-dependency") { + dependencies.push(...getFilesRecursive(message.dir)); + } else if (message.type == "dependency") { + dependencies.push(message.file); + } + } + return dependencies; +} +var src_default = postCSSPlugin; +export { + src_default as default, + defaultOptions +}; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..35a020a --- /dev/null +++ b/dist/index.js @@ -0,0 +1,197 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __markAsModule = (target) => __defProp(target, "__esModule", {value: true}); +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, {get: all[name], enumerable: true}); +}; +var __exportStar = (target, module2, desc) => { + if (module2 && typeof module2 === "object" || typeof module2 === "function") { + for (let key of __getOwnPropNames(module2)) + if (!__hasOwnProp.call(target, key) && key !== "default") + __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable}); + } + return target; +}; +var __toModule = (module2) => { + return __exportStar(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2); +}; +__markAsModule(exports); +__export(exports, { + default: () => src_default, + defaultOptions: () => defaultOptions +}); +var import_fs_extra = __toModule(require("fs-extra")); +var import_util = __toModule(require("util")); +var import_path = __toModule(require("path")); +var import_tmp = __toModule(require("tmp")); +var import_postcss2 = __toModule(require("postcss")); +var import_postcss_modules = __toModule(require("postcss-modules")); +var import_less = __toModule(require("less")); +var import_stylus = __toModule(require("stylus")); +var import_resolve_file = __toModule(require("resolve-file")); +const defaultOptions = { + plugins: [], + modules: true, + rootDir: process.cwd(), + sassOptions: {}, + lessOptions: {}, + stylusOptions: {}, + fileIsModule: null +}; +const postCSSPlugin = ({ + plugins = [], + modules = true, + rootDir = process.cwd(), + sassOptions = {}, + lessOptions = {}, + stylusOptions = {}, + fileIsModule +} = defaultOptions) => ({ + name: "postcss2", + setup(build) { + const tmpDirPath = import_tmp.default.dirSync().name, modulesMap = []; + const modulesPlugin = (0, import_postcss_modules.default)({ + generateScopedName: "[name]__[local]___[hash:base64:5]", + ...typeof modules !== "boolean" ? modules : {}, + getJSON(filepath, json, outpath) { + const mapIndex = modulesMap.findIndex((m) => m.path === filepath); + if (mapIndex !== -1) { + modulesMap[mapIndex].map = json; + } else { + modulesMap.push({ + path: filepath, + map: json + }); + } + if (typeof modules !== "boolean" && typeof modules.getJSON === "function") + return modules.getJSON(filepath, json, outpath); + } + }); + build.onResolve({filter: /.\.(css|sass|scss|less|styl)$/}, async (args) => { + if (args.namespace !== "file" && args.namespace !== "") + return; + let sourceFullPath = (0, import_resolve_file.default)(args.path); + if (!sourceFullPath) + sourceFullPath = import_path.default.resolve(args.resolveDir, args.path); + const sourceExt = import_path.default.extname(sourceFullPath); + const sourceBaseName = import_path.default.basename(sourceFullPath, sourceExt); + const isModule = fileIsModule ? fileIsModule(sourceFullPath) : sourceBaseName.match(/\.module$/); + const sourceDir = import_path.default.dirname(sourceFullPath); + const watchFiles = [sourceFullPath]; + let tmpFilePath; + if (args.kind === "entry-point") { + const sourceRelDir = import_path.default.relative(import_path.default.dirname(rootDir), import_path.default.dirname(sourceFullPath)); + tmpFilePath = import_path.default.resolve(tmpDirPath, sourceRelDir, `${sourceBaseName}.css`); + await (0, import_fs_extra.ensureDir)(import_path.default.dirname(tmpFilePath)); + } else { + const uniqueTmpDir = import_path.default.resolve(tmpDirPath, uniqueId()); + tmpFilePath = import_path.default.resolve(uniqueTmpDir, `${sourceBaseName}.css`); + } + await (0, import_fs_extra.ensureDir)(import_path.default.dirname(tmpFilePath)); + const fileContent = await (0, import_fs_extra.readFile)(sourceFullPath); + let css = sourceExt === ".css" ? fileContent : ""; + if (sourceExt === ".sass" || sourceExt === ".scss") { + const sassResult = await renderSass({ + ...sassOptions, + file: sourceFullPath + }); + css = sassResult.css.toString(); + watchFiles.push(...sassResult.stats.includedFiles); + } + if (sourceExt === ".styl") + css = await renderStylus(new import_util.TextDecoder().decode(fileContent), { + ...stylusOptions, + filename: sourceFullPath + }); + if (sourceExt === ".less") + css = (await import_less.default.render(new import_util.TextDecoder().decode(fileContent), { + ...lessOptions, + filename: sourceFullPath, + rootpath: import_path.default.dirname(args.path) + })).css; + const result = await (0, import_postcss2.default)(isModule ? [modulesPlugin, ...plugins] : plugins).process(css, { + from: sourceFullPath, + to: tmpFilePath + }); + watchFiles.push(...getPostCssDependencies(result.messages)); + await (0, import_fs_extra.writeFile)(tmpFilePath, result.css); + return { + namespace: isModule ? "postcss-module" : "file", + path: tmpFilePath, + watchFiles, + pluginData: { + originalPath: sourceFullPath + } + }; + }); + build.onLoad({filter: /.*/, namespace: "postcss-module"}, async (args) => { + const mod = modulesMap.find(({path: path2}) => path2 === args?.pluginData?.originalPath), resolveDir = import_path.default.dirname(args.path); + return { + resolveDir, + contents: `import ${JSON.stringify(args.path)}; +export default ${JSON.stringify(mod && mod.map ? mod.map : {})};` + }; + }); + } +}); +function renderSass(options) { + return new Promise((resolve, reject) => { + getSassImpl().render(options, (e, res) => { + if (e) + reject(e); + else + resolve(res); + }); + }); +} +function renderStylus(str, options) { + return new Promise((resolve, reject) => { + import_stylus.default.render(str, options, (e, res) => { + if (e) + reject(e); + else + resolve(res); + }); + }); +} +function getSassImpl() { + let impl = "sass"; + try { + require.resolve("sass"); + } catch { + try { + require.resolve("node-sass"); + impl = "node-sass"; + } catch { + throw new Error('Please install "sass" or "node-sass" package'); + } + } + return require(impl); +} +function getFilesRecursive(directory) { + return (0, import_fs_extra.readdirSync)(directory).reduce((files, file) => { + const name = import_path.default.join(directory, file); + return (0, import_fs_extra.statSync)(name).isDirectory() ? [...files, ...getFilesRecursive(name)] : [...files, name]; + }, []); +} +let idCounter = 0; +function uniqueId() { + return Date.now().toString(16) + (idCounter++).toString(16); +} +function getPostCssDependencies(messages) { + let dependencies = []; + for (const message of messages) { + if (message.type == "dir-dependency") { + dependencies.push(...getFilesRecursive(message.dir)); + } else if (message.type == "dependency") { + dependencies.push(message.file); + } + } + return dependencies; +} +var src_default = postCSSPlugin; diff --git a/yarn.lock b/yarn.lock index 06beac4..30e1003 100644 --- a/yarn.lock +++ b/yarn.lock @@ -256,7 +256,7 @@ copy-anything@^2.0.1: cross-env@^7.0.3: version "7.0.3" - resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== dependencies: cross-spawn "^7.0.1" @@ -880,7 +880,7 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@3.1.20, nanoid@^3.1.20: +nanoid@3.1.20: version "3.1.20" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz" integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== @@ -1078,15 +1078,6 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0: resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss@8.x: - version "8.2.8" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.2.8.tgz" - integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.20" - source-map "^0.6.1" - prettier@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz"