Skip to content

Commit

Permalink
Compile styles using shared worker thread (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
giuseppeg authored Jan 25, 2021
1 parent 63bd1e1 commit 4783c43
Show file tree
Hide file tree
Showing 18 changed files with 309 additions and 146 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
- run: yarn install --frozen-lockfile
- run: yarn test
2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
postcss.config.js
test.js
fixture.css
fixtures
5 changes: 0 additions & 5 deletions .travis.yml

This file was deleted.

15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ Use [PostCSS](https://github.com/postcss/postcss) with

⚠️ **This plugin is not actively being maintained. If you want me to work on it please [consider donating](https://github.com/sponsors/giuseppeg).**

## Supporters

Companies and individuals who sponsored some work on this library:

🥇 [@swissredcross](https://github.com/swissredcross)

## Usage

Install this package first.
Expand Down Expand Up @@ -39,7 +45,8 @@ With config:
[
"styled-jsx-plugin-postcss",
{
"path": "[PATH_PREFIX]/postcss.config.js"
"path": "[PATH_PREFIX]/postcss.config.js",
"compileEnv": "worker"
}
]
]
Expand All @@ -49,6 +56,12 @@ With config:
}
```

## compileEnv

When using Node.js v12.3.0 and above the plugin defaults to compiling using a worker thread instead of a child process. This results in faster builds.

If for any reason you want to force compiling using a child process (slower) you can register the plugin with the config option `compileEnv` set to `process`.

### Example with CRA

Usage with Create React App requires you to either _eject_ or use [react-app-rewired](https://github.com/timarney/react-app-rewired).
Expand Down
105 changes: 105 additions & 0 deletions compile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const { spawnSync } = require("child_process");
const path = require("path");
const {
Worker,
receiveMessageOnPort,
MessageChannel,
} = require("worker_threads");
const error = require("./error");

let worker = null;
let unreftimeout = null;

function compileWorker(...args) {
if (unreftimeout) {
clearTimeout(unreftimeout);
unreftimeout = null;
}

if (!worker) {
worker = new Worker(require.resolve("./worker.js"));
}

const signal = new Int32Array(new SharedArrayBuffer(4));
signal[0] = 0;

try {
const subChannel = new MessageChannel();
worker.postMessage({ signal, port: subChannel.port1, args }, [
subChannel.port1,
]);

const workStartedAt = Date.now();
const settings = args[1] || {};
const LOCK_TIMEOUT = settings.lockTimeout || 10000;
Atomics.wait(signal, 0, 0, LOCK_TIMEOUT);

const result = receiveMessageOnPort(subChannel.port2);

if (!result) {
if (Date.now() - workStartedAt >= LOCK_TIMEOUT) {
error(
`postcss is taking more than ${LOCK_TIMEOUT /
1000}s to compile the following styles:\n\n` +
`Filename: ${(settings.babel || {}).filename ||
"unknown filename"}\n\n` +
args[0]
);
}
error(`postcss' compilation result is undefined`);
}
const { message } = result;
if (message.error) {
error(`postcss failed with ${message.error}`);
}
return message.result;
} catch (error) {
throw error;
} finally {
unreftimeout = setTimeout(() => {
worker.unref();
worker = null;
unreftimeout = null;
}, 1000);
}
}

function compileSubprocess(css, settings) {
const result = spawnSync("node", [path.resolve(__dirname, "subprocess.js")], {
input: JSON.stringify({
css,
settings,
}),
encoding: "utf8",
});

if (result.status !== 0) {
if (result.stderr.includes("Invalid PostCSS Plugin")) {
let isNext = false;
try {
require.resolve("next");
isNext = true;
} catch (err) {}
if (isNext) {
console.error(
"Next.js 9 default postcss support uses a non standard postcss config schema https://err.sh/next.js/postcss-shape, you must use the interoperable object-based format instead https://nextjs.org/docs/advanced-features/customizing-postcss-config"
);
}
}

error(`postcss failed with ${result.stderr}`);
}

return result.stdout;
}

module.exports = (css, settings) => {
if (
typeof receiveMessageOnPort === "undefined" ||
settings.compileEnv === "process"
) {
return compileSubprocess(css, settings);
} else {
return compileWorker(css, settings);
}
};
3 changes: 3 additions & 0 deletions error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = function error(message) {
throw new Error(`[styled-jsx-plugin-postcss] ${message}`);
};
12 changes: 0 additions & 12 deletions fixture-postcss-plugin.js

This file was deleted.

File renamed without changes.
14 changes: 14 additions & 0 deletions fixtures/fixture-postcss-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const postcss = require("postcss");

module.exports = (options = {}) => ({
postcssPlugin: "postcss-csso",
Rule(rule, { result, postcss }) {
rule.selector = rule.selector
.split(" ")
.map((s) => `${s}.plugin`)
.join(" ");
return rule;
},
});

module.exports.postcss = true;
File renamed without changes.
11 changes: 11 additions & 0 deletions fixtures/timeout/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const postcss = require("postcss");

module.exports = (options = {}) => ({
postcssPlugin: "postcss-break",
async Rule(rule, { result, postcss }) {
await new Promise((resolve) => setTimeout(resolve, 20000));
return rule;
},
});

module.exports.postcss = true;
7 changes: 7 additions & 0 deletions fixtures/timeout/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const path = require("path");

module.exports = {
plugins: {
[require.resolve("./plugin.js")]: {},
},
};
33 changes: 8 additions & 25 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,21 @@
const { spawnSync } = require("child_process");
const path = require("path");
const compile = require("./compile");
const error = require("./error");

module.exports = (css, settings) => {
const cssWithPlaceholders = css.replace(
/%%styled-jsx-placeholder-(\d+)%%/g,
(_, id) => `/*%%styled-jsx-placeholder-${id}%%*/`
);

const result = spawnSync("node", [path.resolve(__dirname, "processor.js")], {
input: JSON.stringify({
css: cssWithPlaceholders,
settings,
}),
encoding: "utf8",
});
const result = compile(cssWithPlaceholders, settings);

if (result.status !== 0) {
if (result.stderr.includes("Invalid PostCSS Plugin")) {
let isNext = false;
try {
require.resolve("next");
isNext = true;
} catch (err) {}
if (isNext) {
console.error(
"Next.js 9 default postcss support uses a non standard postcss config schema https://err.sh/next.js/postcss-shape, you must use the interoperable object-based format instead https://nextjs.org/docs/advanced-features/customizing-postcss-config"
);
}
}

throw new Error(`postcss failed with ${result.stderr}`);
if (!result) {
error(
`did not compile the following CSS:\n\n${css.split("\n").join("\n\t")}\n`
);
}

return result.stdout.replace(
return result.replace(
/\/\*%%styled-jsx-placeholder-(\d+)%%\*\//g,
(_, id) => `%%styled-jsx-placeholder-${id}%%`
);
Expand Down
2 changes: 1 addition & 1 deletion postcss.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ module.exports = {
},
},
"postcss-calc": {},
[path.resolve(__dirname, "./fixture-postcss-plugin.js")]: {},
[path.resolve(__dirname, "./fixtures/fixture-postcss-plugin.js")]: {},
},
};
57 changes: 15 additions & 42 deletions processor.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,20 @@
const postcss = require("postcss");
const loader = require("postcss-load-config");
const postcss = require('postcss')
const loader = require('postcss-load-config')

let plugins;
let _processor;
const loaderPromises = {}

function processor(src, options) {
options = options || {};
let loaderPromise;
if (!plugins) {
loaderPromise = loader(options.env || process.env, options.path, {
argv: false,
}).then((pluginsInfo) => {
plugins = pluginsInfo.plugins || [];
});
} else {
loaderPromise = Promise.resolve();
}
module.exports = function processor(src, options) {
options = options || {}

return loaderPromise
.then(() => {
if (!_processor) {
_processor = postcss(plugins);
}
return _processor.process(src, { from: false });
})
.then((result) => result.css);
}
const loaderPromise = loaderPromises.hasOwnProperty(options.path || 'auto')
? loaderPromises[options.path || 'auto']
: loader(options.env || process.env, options.path, {
argv: false
}).then((pluginsInfo) => pluginsInfo.plugins || [])

let input = "";
process.stdin.on("data", (data) => {
input += data.toString();
});
loaderPromises[options.path || 'auto'] = loaderPromise

process.stdin.on("end", () => {
const inputData = JSON.parse(input);
processor(inputData.css, inputData.settings)
.then((result) => {
process.stdout.write(result);
})
.catch((err) => {
// NOTE: we console.erorr(err) and then process.exit(1) instead of throwing the error
// to avoid the UnhandledPromiseRejectionWarning message.
console.error(err);
process.exit(1);
});
});
return loaderPromise
.then((plugins) => postcss(plugins).process(src, { from: false }))
.then((result) => result.css)
}
20 changes: 20 additions & 0 deletions subprocess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const processor = require("./processor");

let input = "";
process.stdin.on("data", (data) => {
input += data.toString();
});

process.stdin.on("end", () => {
const inputData = JSON.parse(input);
processor(inputData.css, inputData.settings)
.then((result) => {
process.stdout.write(result);
})
.catch((err) => {
// NOTE: we console.error(err) and then process.exit(1) instead of throwing the error
// to avoid the UnhandledPromiseRejectionWarning message.
console.error(err);
process.exit(1);
});
});
Loading

0 comments on commit 4783c43

Please sign in to comment.