@@ -0,0 +1,6 @@ | |||
node_modules | |||
build | |||
yarn-error.log | |||
test/dist | |||
package-lock.json | |||
test/dist |
@@ -0,0 +1,8 @@ | |||
# Default ignored files | |||
/shelf/ | |||
/workspace.xml | |||
# Datasource local storage ignored files | |||
/dataSources/ | |||
/dataSources.local.xml | |||
# Editor-based HTTP Client requests | |||
/httpRequests/ |
@@ -0,0 +1,49 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="4"> | |||
<component name="PublishConfigData"> | |||
<serverData> | |||
<paths name="MAGDY SERVER 6"> | |||
<serverdata> | |||
<mappings> | |||
<mapping local="$PROJECT_DIR$" web="/" /> | |||
</mappings> | |||
</serverdata> | |||
</paths> | |||
<paths name="MAGDY_SAPOS"> | |||
<serverdata> | |||
<mappings> | |||
<mapping local="$PROJECT_DIR$" web="/" /> | |||
</mappings> | |||
</serverdata> | |||
</paths> | |||
<paths name="MEMBTECH-OFFLINE-POS"> | |||
<serverdata> | |||
<mappings> | |||
<mapping local="$PROJECT_DIR$" web="/" /> | |||
</mappings> | |||
</serverdata> | |||
</paths> | |||
<paths name="MEMBTECH-STORE"> | |||
<serverdata> | |||
<mappings> | |||
<mapping local="$PROJECT_DIR$" web="/" /> | |||
</mappings> | |||
</serverdata> | |||
</paths> | |||
<paths name="MEMBTECH_COM"> | |||
<serverdata> | |||
<mappings> | |||
<mapping local="$PROJECT_DIR$" web="/" /> | |||
</mappings> | |||
</serverdata> | |||
</paths> | |||
<paths name="WOLF"> | |||
<serverdata> | |||
<mappings> | |||
<mapping local="$PROJECT_DIR$" web="/" /> | |||
</mappings> | |||
</serverdata> | |||
</paths> | |||
</serverData> | |||
</component> | |||
</project> |
@@ -0,0 +1,8 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<module type="WEB_MODULE" version="4"> | |||
<component name="NewModuleRootManager"> | |||
<content url="file://$MODULE_DIR$" /> | |||
<orderEntry type="inheritedJdk" /> | |||
<orderEntry type="sourceFolder" forTests="false" /> | |||
</component> | |||
</module> |
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="4"> | |||
<component name="JavaScriptSettings"> | |||
<option name="languageLevel" value="ES6" /> | |||
</component> | |||
</project> |
@@ -0,0 +1,8 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="4"> | |||
<component name="ProjectModuleManager"> | |||
<modules> | |||
<module fileurl="file://$PROJECT_DIR$/.idea/esbuild-plugin-postcss2.iml" filepath="$PROJECT_DIR$/.idea/esbuild-plugin-postcss2.iml" /> | |||
</modules> | |||
</component> | |||
</project> |
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project version="4"> | |||
<component name="VcsDirectoryMappings"> | |||
<mapping directory="$PROJECT_DIR$" vcs="Git" /> | |||
</component> | |||
</project> |
@@ -0,0 +1 @@ | |||
dist/test |
@@ -0,0 +1,3 @@ | |||
node_modules | |||
.github | |||
dist |
@@ -0,0 +1,3 @@ | |||
{ | |||
"trailingComma": "none" | |||
} |
@@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2021 Marton Lederer | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,89 @@ | |||
# esbuild-plugin-postcss2 | |||
This plugin is an optimized, type-friendly version of [esbuild-plugin-postcss](https://github.com/deanc/esbuild-plugin-postcss). It supports CSS preprocessors and CSS modules. | |||
## Install | |||
```sh | |||
yarn add -D esbuild-plugin-postcss2 | |||
``` | |||
or | |||
```sh | |||
npm i -D esbuild-plugin-postcss2 | |||
``` | |||
## Usage | |||
Add the plugin to your esbuild plugins: | |||
```js | |||
const esbuild = require("esbuild"); | |||
const postCssPlugin = require("esbuild-plugin-postcss2"); | |||
esbuild.build({ | |||
... | |||
plugins: [ | |||
postCssPlugin.default() | |||
] | |||
... | |||
}); | |||
``` | |||
### PostCSS plugins | |||
Add your desired PostCSS plugin to the plugins array: | |||
```js | |||
const autoprefixer = require("autoprefixer"); | |||
esbuild.build({ | |||
... | |||
plugins: [ | |||
postCssPlugin.default({ | |||
plugins: [autoprefixer] | |||
}) | |||
] | |||
... | |||
}); | |||
``` | |||
### CSS modules | |||
PostCSS modules are enabled by default. You can pass in a config or disable it with the `modules` field: | |||
```js | |||
postCssPlugin.default({ | |||
// pass in `postcss-modules` custom options | |||
// set to false to disable | |||
modules: { | |||
getJSON(cssFileName, json, outputFileName) { | |||
const path = require("path"); | |||
const cssName = path.basename(cssFileName, ".css"); | |||
const jsonFileName = path.resolve("./build/" + cssName + ".json"); | |||
fs.writeFileSync(jsonFileName, JSON.stringify(json)); | |||
} | |||
} | |||
}); | |||
``` | |||
As per standard any file having `module` before the extension (ie `somefile.module.css`) will be treated as a module. | |||
The option `fileIsModule` allows to override this behavior. | |||
```js | |||
postCssPlugin.default({ | |||
// pass a custom `fileIsModule` option to tell whether a file should be treated as a module | |||
// in this example we want everything to be a module except file finishing with `global.css` | |||
fileIsModule: (filepath) => !filepath.endsWith(".global.css") | |||
}); | |||
``` | |||
### Preprocessors | |||
To use preprocessors (`sass`, `scss`, `stylus`, `less`), just add the desired preprocessor as a `devDependency`: | |||
```sh | |||
yarn add -D sass | |||
``` |
@@ -0,0 +1,16 @@ | |||
const { build } = require("esbuild"), | |||
{ copyFile } = require("fs"); | |||
const production = process.env.NODE_ENV === "production", | |||
formats = ["cjs", "esm"]; | |||
(async () => { | |||
for (const format of formats) { | |||
await build({ | |||
entryPoints: ["./src/index.ts"], | |||
watch: !production, | |||
format, | |||
outfile: `./dist/index${format === "cjs" ? "" : "." + format}.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 | |||
}; |
@@ -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; |
@@ -0,0 +1,71 @@ | |||
{ | |||
"name": "@xhiveframework/esbuild-plugin-postcss2", | |||
"version": "0.1.3", | |||
"description": "Use postcss with esbuild", | |||
"repository": { | |||
"type": "git", | |||
"url": "git+https://github.com/xhiveframework/esbuild-plugin-postcss2.git" | |||
}, | |||
"author": "Marton Lederer <marton@lederer.hu>", | |||
"license": "MIT", | |||
"private": false, | |||
"publishConfig": { | |||
"access": "public" | |||
}, | |||
"scripts": { | |||
"build": "cross-env NODE_ENV=production node build.js", | |||
"dev": "cross-env NODE_ENV=development node build.js", | |||
"test": "yarn build && cd test && mocha 'index.js' --no-timeout --exit", | |||
"fmt": "prettier --write .", | |||
"fmt:check": "prettier --check ." | |||
}, | |||
"gitHooks": { | |||
"pre-commit": "prettier --write . && git add -A" | |||
}, | |||
"files": [ | |||
"dist", | |||
"src/modules.d.ts" | |||
], | |||
"main": "dist/index.js", | |||
"module": "dist/index.esm.js", | |||
"types": "src/modules.d.ts", | |||
"dependencies": { | |||
"autoprefixer": "^10.2.5", | |||
"fs-extra": "^9.1.0", | |||
"less": "^4.x", | |||
"postcss-modules": "^4.0.0", | |||
"resolve-file": "^0.3.0", | |||
"sass": "^1.x", | |||
"stylus": "^0.x", | |||
"tmp": "^0.2.1" | |||
}, | |||
"devDependencies": { | |||
"@types/chai": "^4.2.15", | |||
"@types/fs-extra": "^9.0.9", | |||
"@types/less": "^3.0.2", | |||
"@types/mocha": "^8.2.2", | |||
"@types/node": "^14.14.37", | |||
"@types/sass": "^1.16.0", | |||
"@types/stylus": "^0.48.33", | |||
"@types/tmp": "^0.2.0", | |||
"chai": "^4.3.4", | |||
"cross-env": "^7.0.3", | |||
"esbuild": "^0.11.2", | |||
"mocha": "^8.3.2", | |||
"normalize.css": "^8.0.1", | |||
"postcss-import": "^14.0.2", | |||
"prettier": "^2.2.1", | |||
"typescript": "^4.2.3", | |||
"yorkie": "^2.0.0" | |||
}, | |||
"peerDependencies": { | |||
"less": "^4.x", | |||
"postcss": "8.x", | |||
"sass": "^1.x", | |||
"stylus": "^0.x" | |||
}, | |||
"bugs": { | |||
"url": "https://github.com/xhiveframework/esbuild-plugin-postcss2/issues" | |||
}, | |||
"homepage": "https://github.com/xhiveframework/esbuild-plugin-postcss2#readme" | |||
} |
@@ -0,0 +1,275 @@ | |||
import { Plugin } from "esbuild"; | |||
import { Plugin as PostCSSPlugin, Message } from "postcss"; | |||
import { | |||
ensureDir, | |||
readFile, | |||
readdirSync, | |||
statSync, | |||
writeFile | |||
} from "fs-extra"; | |||
import { TextDecoder } from "util"; | |||
import { | |||
SassException, | |||
Result as SassResult, | |||
Options as SassOptions | |||
} from "sass"; | |||
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"; | |||
type StylusRenderOptions = Parameters<typeof stylus.render>[1]; // The Stylus.RenderOptions interface doesn't seem to be exported... So next best | |||
interface PostCSSPluginOptions { | |||
plugins: PostCSSPlugin[]; | |||
modules: boolean | any; | |||
rootDir?: string; | |||
sassOptions?: SassOptions; | |||
lessOptions?: Less.Options; | |||
stylusOptions?: StylusRenderOptions; | |||
fileIsModule?: (filename: string) => boolean; | |||
} | |||
interface CSSModule { | |||
path: string; | |||
map: { | |||
[key: string]: string; | |||
}; | |||
} | |||
export const defaultOptions: PostCSSPluginOptions = { | |||
plugins: [], | |||
modules: true, | |||
rootDir: process.cwd(), | |||
sassOptions: {}, | |||
lessOptions: {}, | |||
stylusOptions: {}, | |||
fileIsModule: null | |||
}; | |||
const postCSSPlugin = ({ | |||
plugins = [], | |||
modules = true, | |||
rootDir = process.cwd(), | |||
sassOptions = {}, | |||
lessOptions = {}, | |||
stylusOptions = {}, | |||
fileIsModule | |||
}: PostCSSPluginOptions = defaultOptions): Plugin => ({ | |||
name: "postcss2", | |||
setup(build) { | |||
// get a temporary path where we can save compiled CSS | |||
const tmpDirPath = tmp.dirSync().name, | |||
modulesMap: CSSModule[] = []; | |||
const modulesPlugin = postcssModules({ | |||
generateScopedName: "[name]__[local]___[hash:base64:5]", | |||
...(typeof modules !== "boolean" ? modules : {}), | |||
getJSON(filepath, json, outpath) { | |||
// Make sure to replace json map instead of pushing new map everytime with edit file on watch | |||
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) => { | |||
// Namespace is empty when using CSS as an entrypoint | |||
if (args.namespace !== "file" && args.namespace !== "") return; | |||
// Resolve files from node_modules (ex: npm install normalize.css) | |||
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: string; | |||
if (args.kind === "entry-point") { | |||
// For entry points, we use <tempdir>/<path-within-project-root>/<file-name>.css | |||
const sourceRelDir = path.relative( | |||
path.dirname(rootDir), | |||
path.dirname(sourceFullPath) | |||
); | |||
tmpFilePath = path.resolve( | |||
tmpDirPath, | |||
sourceRelDir, | |||
`${sourceBaseName}.css` | |||
); | |||
await ensureDir(path.dirname(tmpFilePath)); | |||
} else { | |||
// For others, we use <tempdir>/<unique-directory-name>/<file-name>.css | |||
// | |||
// This is a workaround for the following esbuild issue: | |||
// https://github.com/evanw/esbuild/issues/1101 | |||
// | |||
// esbuild is unable to find the file, even though it does exist. This only | |||
// happens for files in a directory with several other entries, so by | |||
// creating a unique directory name per file on every build, we guarantee | |||
// that there will only every be a single file present within the directory, | |||
// circumventing the esbuild issue. | |||
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 : ""; | |||
// parse files with preprocessors | |||
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; | |||
// wait for plugins to complete parsing & get result | |||
const result = await postcss( | |||
isModule ? [modulesPlugin, ...plugins] : plugins | |||
).process(css, { | |||
from: sourceFullPath, | |||
to: tmpFilePath | |||
}); | |||
watchFiles.push(...getPostCssDependencies(result.messages)); | |||
// Write result CSS | |||
await writeFile(tmpFilePath, result.css); | |||
return { | |||
namespace: isModule ? "postcss-module" : "file", | |||
path: tmpFilePath, | |||
watchFiles, | |||
pluginData: { | |||
originalPath: sourceFullPath | |||
} | |||
}; | |||
} | |||
); | |||
// load css modules | |||
build.onLoad( | |||
{ filter: /.*/, namespace: "postcss-module" }, | |||
async (args) => { | |||
const mod = modulesMap.find( | |||
({ path }) => path === args?.pluginData?.originalPath | |||
), | |||
resolveDir = path.dirname(args.path); | |||
return { | |||
resolveDir, | |||
contents: `import ${JSON.stringify( | |||
args.path | |||
)};\nexport default ${JSON.stringify(mod && mod.map ? mod.map : {})};` | |||
}; | |||
} | |||
); | |||
} | |||
}); | |||
function renderSass(options: SassOptions): Promise<SassResult> { | |||
return new Promise((resolve, reject) => { | |||
getSassImpl().render(options, (e: SassException, res: SassResult) => { | |||
if (e) reject(e); | |||
else resolve(res); | |||
}); | |||
}); | |||
} | |||
function renderStylus( | |||
str: string, | |||
options: StylusRenderOptions | |||
): Promise<string> { | |||
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: string): string[] { | |||
return readdirSync(directory).reduce((files, file) => { | |||
const name = path.join(directory, file); | |||
return statSync(name).isDirectory() | |||
? [...files, ...getFilesRecursive(name)] | |||
: [...files, name]; | |||
}, []); | |||
} | |||
let idCounter = 0; | |||
/** | |||
* Generates an id that is guaranteed to be unique for the Node.JS instance. | |||
*/ | |||
function uniqueId(): string { | |||
return Date.now().toString(16) + (idCounter++).toString(16); | |||
} | |||
function getPostCssDependencies(messages: Message[]): string[] { | |||
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; | |||
} | |||
export default postCSSPlugin; |
@@ -0,0 +1,25 @@ | |||
// css module files | |||
declare module "*.module.css" { | |||
const classes: { readonly [key: string]: string }; | |||
export default classes; | |||
} | |||
declare module "*.module.scss" { | |||
const classes: { readonly [key: string]: string }; | |||
export default classes; | |||
} | |||
declare module "*.module.sass" { | |||
const classes: { readonly [key: string]: string }; | |||
export default classes; | |||
} | |||
declare module "*.module.less" { | |||
const classes: { readonly [key: string]: string }; | |||
export default classes; | |||
} | |||
declare module "*.module.styl" { | |||
const classes: { readonly [key: string]: string }; | |||
export default classes; | |||
} |
@@ -0,0 +1,154 @@ | |||
const autoprefixer = require("autoprefixer"), | |||
postCssImport = require("postcss-import"), | |||
{ build } = require("esbuild"), | |||
postCSS = require("../dist"), | |||
{ assert } = require("chai"), | |||
fs = require("fs"); | |||
describe("PostCSS esbuild tests", () => { | |||
it("Works with basic CSS imports", (done) => { | |||
test(["tests/basic.ts"]) | |||
.then((res) => { | |||
assert(res); | |||
done(); | |||
}) | |||
.catch(done); | |||
}); | |||
it("Works with preprocessors", (done) => { | |||
test(["tests/preprocessors.ts"]) | |||
.then((res) => { | |||
assert(res); | |||
done(); | |||
}) | |||
.catch(done); | |||
}); | |||
it("Works with CSS modules", (done) => { | |||
test(["tests/modules.ts"]) | |||
.then((res) => { | |||
assert(res); | |||
done(); | |||
}) | |||
.catch(done); | |||
}); | |||
it("Works with CSS as entrypoint", (done) => { | |||
test(["tests/styles.css", "tests/styles2.css"]) | |||
.then((res) => { | |||
assert(res); | |||
done(); | |||
}) | |||
.catch(done); | |||
}); | |||
it("Works with node_modules import", (done) => { | |||
test(["tests/node_modules.ts"]) | |||
.then((res) => { | |||
assert(res); | |||
done(); | |||
}) | |||
.catch(done); | |||
}); | |||
it("Works while waching css files directly", (done) => { | |||
let notTriggerTimeout = null; | |||
build({ | |||
entryPoints: ["tests/watch.ts"], | |||
bundle: true, | |||
outdir: "dist", | |||
watch: { | |||
onRebuild: (error, result) => { | |||
notTriggerTimeout = null; | |||
if (error) return done(error); | |||
assert(result); | |||
done(); | |||
} | |||
}, | |||
plugins: [ | |||
postCSS.default({ | |||
plugins: [autoprefixer] | |||
}) | |||
] | |||
}) | |||
.then(() => { | |||
// test if modifying the css actually triggers the onRebuild event | |||
const data = `.Test { display: block; }`; | |||
fs.writeFile("./styles/watch.css", data, (err) => { | |||
if (err) return done(err); | |||
notTriggerTimeout = setTimeout(() => { | |||
done("Watch file not triggered!"); | |||
}, 1000); | |||
}); | |||
}) | |||
.catch(() => process.exit(1)); | |||
}); | |||
it("Works while waching css files through dependencies", (done) => { | |||
let notTriggerTimeout = null; | |||
build({ | |||
entryPoints: ["tests/watch2.ts"], | |||
bundle: true, | |||
outdir: "dist", | |||
watch: { | |||
onRebuild: (error, result) => { | |||
notTriggerTimeout = null; | |||
if (error) return done(error); | |||
assert(result); | |||
done(); | |||
} | |||
}, | |||
plugins: [ | |||
postCSS.default({ | |||
plugins: [autoprefixer, postCssImport] | |||
}) | |||
] | |||
}) | |||
.then(() => { | |||
// test if modifying the css actually triggers the onRebuild event | |||
const data = `.Test { display: block; }`; | |||
fs.writeFile("./styles/watch3.css", data, (err) => { | |||
if (err) return done(err); | |||
notTriggerTimeout = setTimeout(() => { | |||
done("Watch file not triggered!"); | |||
}, 1000); | |||
}); | |||
}) | |||
.catch(() => process.exit(1)); | |||
}); | |||
it("Works with custom module function", (done) => { | |||
let testFilename = null; | |||
build({ | |||
entryPoints: ["tests/basic.ts"], | |||
bundle: true, | |||
outdir: "dist", | |||
plugins: [ | |||
postCSS.default({ | |||
plugins: [autoprefixer, postCssImport], | |||
modules: true, | |||
fileIsModule: (filename) => { | |||
testFilename = filename; | |||
return false; | |||
} | |||
}) | |||
] | |||
}) | |||
.then(() => { | |||
// ensure the proper filename was passed | |||
assert.match(testFilename, /styles\/basic\.css/); | |||
}) | |||
.catch((e) => { | |||
console.error(e); | |||
process.exit(1); | |||
}); | |||
}); | |||
}); | |||
function test(entryPoint) { | |||
return build({ | |||
entryPoints: entryPoint, | |||
bundle: true, | |||
outdir: "dist", | |||
plugins: [ | |||
postCSS.default({ | |||
plugins: [autoprefixer] | |||
}) | |||
] | |||
}).catch(() => process.exit(1)); | |||
} |
@@ -0,0 +1,3 @@ | |||
.Test { | |||
display: block; | |||
} |
@@ -0,0 +1,11 @@ | |||
.TestModule { | |||
align-items: center; | |||
} | |||
.TestModuleAnother { | |||
justify-content: space-between; | |||
} | |||
.TextModuleLast { | |||
width: 100vw; | |||
} |
@@ -0,0 +1,5 @@ | |||
@text: left; | |||
.TestLessModule { | |||
text-align: @text; | |||
} |
@@ -0,0 +1,2 @@ | |||
.TestModuleSass | |||
display: flex |
@@ -0,0 +1,3 @@ | |||
.Test { | |||
display: block; | |||
} |
@@ -0,0 +1,6 @@ | |||
$test: 20px | |||
.SassClass | |||
text-transform: uppercase | |||
display: flex | |||
font-size: $test |
@@ -0,0 +1,8 @@ | |||
$test: translate(-50%, -50%); | |||
.ScssClass { | |||
position: relative; | |||
top: 50%; | |||
left: 50%; | |||
transform: $test; | |||
} |
@@ -0,0 +1,4 @@ | |||
test = 25px | |||
.StylusClass | |||
margin test |
@@ -0,0 +1,3 @@ | |||
.Test { | |||
display: block; | |||
} |
@@ -0,0 +1,5 @@ | |||
@import "./watch3.css"; | |||
.Test { | |||
display: block; | |||
} |
@@ -0,0 +1,3 @@ | |||
.Test { | |||
display: block; | |||
} |
@@ -0,0 +1 @@ | |||
import "../styles/basic.css"; |
@@ -0,0 +1,5 @@ | |||
import styles from "../styles/example.module.sass"; | |||
import styles2 from "../styles/example.module.css"; | |||
import styles3 from "../styles/example.module.less"; | |||
console.log(styles, styles2, styles3); |
@@ -0,0 +1 @@ | |||
import "normalize.css"; |
@@ -0,0 +1,4 @@ | |||
import "../styles/preprocessors.sass"; | |||
import "../styles/preprocessors.scss"; | |||
import "../styles/preprocessors.less"; | |||
import "../styles/preprocessors.styl"; |
@@ -0,0 +1,6 @@ | |||
.example { | |||
display: grid; | |||
transition: all 0.5s; | |||
user-select: none; | |||
background: linear-gradient(to bottom, white, black); | |||
} |
@@ -0,0 +1,6 @@ | |||
.example { | |||
display: grid; | |||
transition: all 0.5s; | |||
user-select: none; | |||
background: linear-gradient(to bottom, white, black); | |||
} |
@@ -0,0 +1 @@ | |||
import "../styles/watch.css"; |
@@ -0,0 +1 @@ | |||
import "../styles/watch2.css"; |
@@ -0,0 +1,13 @@ | |||
{ | |||
"compilerOptions": { | |||
"esModuleInterop": true, | |||
"moduleResolution": "node", | |||
"module": "commonjs", | |||
"target": "es2017", | |||
"outDir": "dist", | |||
"declaration": true, | |||
"rootDir": "src" | |||
}, | |||
"include": ["src"], | |||
"exclude": ["node_modules", "dist"] | |||
} |