chore: basic plugin
This commit is contained in:
commit
8778057fd7
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
yarn-error.log
|
||||||
|
test/dist
|
||||||
1
.npmignore
Normal file
1
.npmignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
dist/test
|
||||||
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
.github
|
||||||
|
dist
|
||||||
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
||||||
17
build.js
Normal file
17
build.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const { build } = require("esbuild"),
|
||||||
|
{ copyFile } = require("fs");
|
||||||
|
|
||||||
|
const production = process.env.NODE_ENV === "production";
|
||||||
|
|
||||||
|
build({
|
||||||
|
entryPoints: ["./src/index.ts"],
|
||||||
|
watch: !production,
|
||||||
|
format: "cjs",
|
||||||
|
outfile: `./dist/index.js`
|
||||||
|
}).catch(() => process.exit(1));
|
||||||
|
|
||||||
|
// copy module declarations
|
||||||
|
copyFile("./src/modules.d.ts", "./dist/modules.d.ts", (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log("[modules.d.ts] copied");
|
||||||
|
});
|
||||||
60
package.json
Normal file
60
package.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "esbuild-plugin-postcss2",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Use postcss with esbuild",
|
||||||
|
"repository": "https://github.com/martonlederer/esbuild-plugin-postcss2",
|
||||||
|
"author": "Marton Lederer <marton@lederer.hu>",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": false,
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "NODE_ENV=production node build.js && tsc --emitDeclarationOnly",
|
||||||
|
"dev": "NODE_ENV=development node build.js",
|
||||||
|
"test": "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"
|
||||||
|
],
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"dependencies": {
|
||||||
|
"autoprefixer": "^10.2.4",
|
||||||
|
"fs-extra": "^9.1.0",
|
||||||
|
"less": "^4.1.1",
|
||||||
|
"postcss": "^8.2.6",
|
||||||
|
"postcss-modules": "^4.0.0",
|
||||||
|
"sass": "^1.32.8",
|
||||||
|
"stylus": "^0.54.8",
|
||||||
|
"tmp": "^0.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/chai": "^4.2.15",
|
||||||
|
"@types/fs-extra": "^9.0.7",
|
||||||
|
"@types/less": "^3.0.2",
|
||||||
|
"@types/mocha": "^8.2.1",
|
||||||
|
"@types/node": "^14.14.28",
|
||||||
|
"@types/sass": "^1.16.0",
|
||||||
|
"@types/stylus": "^0.48.33",
|
||||||
|
"@types/tmp": "^0.2.0",
|
||||||
|
"chai": "^4.3.0",
|
||||||
|
"esbuild": "^0.8.49",
|
||||||
|
"mocha": "^8.3.0",
|
||||||
|
"prettier": "^2.2.1",
|
||||||
|
"typescript": "^4.1.5",
|
||||||
|
"yorkie": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"esbuild": "^0.8.46",
|
||||||
|
"less": "^4.x",
|
||||||
|
"postcss": "8.x",
|
||||||
|
"sass": "^1.x",
|
||||||
|
"stylus": "^0.x"
|
||||||
|
}
|
||||||
|
}
|
||||||
148
src/index.ts
Normal file
148
src/index.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { Plugin } from "esbuild";
|
||||||
|
import { Plugin as PostCSSPlugin } from "postcss";
|
||||||
|
import { ensureDir, readFile, writeFile } from "fs-extra";
|
||||||
|
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";
|
||||||
|
|
||||||
|
interface PostCSSPluginOptions {
|
||||||
|
plugins: PostCSSPlugin[];
|
||||||
|
modules: boolean | any;
|
||||||
|
rootDir?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CSSModule {
|
||||||
|
path: string;
|
||||||
|
map: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const postCSSPlugin = ({
|
||||||
|
plugins = [],
|
||||||
|
modules = true,
|
||||||
|
rootDir = process.cwd()
|
||||||
|
}: PostCSSPluginOptions): Plugin => ({
|
||||||
|
name: "postcss2",
|
||||||
|
setup(build) {
|
||||||
|
// get a temporary path where we can save compiled CSS
|
||||||
|
const tmpDirPath = tmp.dirSync().name,
|
||||||
|
modulesMap: CSSModule[] = [];
|
||||||
|
|
||||||
|
build.onResolve(
|
||||||
|
{ filter: /.\.(css|sass|scss|less|styl)$/, namespace: "file" },
|
||||||
|
async (args) => {
|
||||||
|
const sourceFullPath = path.resolve(args.resolveDir, args.path),
|
||||||
|
sourceExt = path.extname(sourceFullPath),
|
||||||
|
sourceBaseName = path.basename(sourceFullPath, sourceExt),
|
||||||
|
sourceDir = path.dirname(sourceFullPath),
|
||||||
|
sourceRelDir = path.relative(path.dirname(rootDir), sourceDir),
|
||||||
|
isModule = sourceBaseName.match(/\.module$/),
|
||||||
|
tmpDir = path.resolve(tmpDirPath, sourceRelDir),
|
||||||
|
tmpFilePath = path.resolve(
|
||||||
|
tmpDir,
|
||||||
|
`${sourceBaseName}-tmp-${Date.now()}${
|
||||||
|
isModule ? ".module" : ""
|
||||||
|
}.css`
|
||||||
|
);
|
||||||
|
|
||||||
|
await ensureDir(tmpDir);
|
||||||
|
|
||||||
|
const fileContent = await readFile(sourceFullPath);
|
||||||
|
let css = sourceExt === ".css" ? fileContent : "";
|
||||||
|
|
||||||
|
// parse css modules with postcss-modules
|
||||||
|
if (modules !== false && isModule) {
|
||||||
|
plugins.unshift(
|
||||||
|
postcssModules({
|
||||||
|
...(typeof modules !== "boolean" ? modules : {}),
|
||||||
|
getJSON(filepath, json, outpath) {
|
||||||
|
modulesMap.push({
|
||||||
|
path: tmpFilePath,
|
||||||
|
map: json
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof modules !== "boolean" &&
|
||||||
|
typeof modules.getJSON === "function"
|
||||||
|
)
|
||||||
|
return modules.getJSON(filepath, json, outpath);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse files with preprocessors
|
||||||
|
if (sourceExt === ".sass" || sourceExt === ".scss")
|
||||||
|
css = (await renderSass({ file: sourceFullPath })).css.toString();
|
||||||
|
if (sourceExt === ".styl")
|
||||||
|
css = await renderStylus(new TextDecoder().decode(fileContent), {
|
||||||
|
filename: sourceFullPath
|
||||||
|
});
|
||||||
|
if (sourceExt === ".less")
|
||||||
|
css = (
|
||||||
|
await less.render(new TextDecoder().decode(fileContent), {
|
||||||
|
filename: sourceFullPath,
|
||||||
|
rootpath: path.dirname(args.path)
|
||||||
|
})
|
||||||
|
).css;
|
||||||
|
|
||||||
|
// wait for plugins to complete parsing & get result
|
||||||
|
const result = await postcss(plugins).process(css, {
|
||||||
|
from: sourceFullPath,
|
||||||
|
to: tmpFilePath
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write result CSS
|
||||||
|
await writeFile(tmpFilePath, result.css);
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: tmpFilePath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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): 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default postCSSPlugin;
|
||||||
15
src/modules.d.ts
vendored
Normal file
15
src/modules.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
36
test/index.js
Normal file
36
test/index.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const autoprefixer = require("autoprefixer"),
|
||||||
|
{ build } = require("esbuild"),
|
||||||
|
postCSS = require("../dist"),
|
||||||
|
{ assert } = require("chai");
|
||||||
|
|
||||||
|
describe("PostCSS esbuild tests", () => {
|
||||||
|
it("Works with basic CSS imports", (done) => {
|
||||||
|
test("basic")
|
||||||
|
.then((res) => {
|
||||||
|
assert(res);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
it("Works with preprocessors", (done) => {
|
||||||
|
test("preprocessors")
|
||||||
|
.then((res) => {
|
||||||
|
assert(res);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function test(test) {
|
||||||
|
return build({
|
||||||
|
entryPoints: [`tests/${test}.ts`],
|
||||||
|
bundle: true,
|
||||||
|
outdir: "dist",
|
||||||
|
plugins: [
|
||||||
|
postCSS.default({
|
||||||
|
plugins: [autoprefixer]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}).catch(() => process.exit(1));
|
||||||
|
}
|
||||||
3
test/styles/basic.css
Normal file
3
test/styles/basic.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.Test {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
5
test/styles/preprocessors.less
Normal file
5
test/styles/preprocessors.less
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@test: 2em;
|
||||||
|
|
||||||
|
.LessClass {
|
||||||
|
height: @test;
|
||||||
|
}
|
||||||
6
test/styles/preprocessors.sass
Normal file
6
test/styles/preprocessors.sass
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
$test: 20px
|
||||||
|
|
||||||
|
.SassClass
|
||||||
|
text-transform: uppercase
|
||||||
|
display: flex
|
||||||
|
font-size: $test
|
||||||
8
test/styles/preprocessors.scss
Normal file
8
test/styles/preprocessors.scss
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
$test: translate(-50%, -50%);
|
||||||
|
|
||||||
|
.ScssClass {
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: $test;
|
||||||
|
}
|
||||||
4
test/styles/preprocessors.styl
Normal file
4
test/styles/preprocessors.styl
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
test = 25px
|
||||||
|
|
||||||
|
.StylusClass
|
||||||
|
margin test
|
||||||
1
test/tests/basic.ts
Normal file
1
test/tests/basic.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
import "../styles/basic.css";
|
||||||
4
test/tests/preprocessors.ts
Normal file
4
test/tests/preprocessors.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import "../styles/preprocessors.sass";
|
||||||
|
import "../styles/preprocessors.scss";
|
||||||
|
import "../styles/preprocessors.less";
|
||||||
|
import "../styles/preprocessors.styl";
|
||||||
13
tsconfig.json
Normal file
13
tsconfig.json
Normal file
@ -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"]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user