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