commit 1ed54ce4817fb4ec26fcb8f1e789a5dc1930d5a3 Author: Ruthenic Date: Wed Aug 31 21:03:14 2022 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..00c3041 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b394677 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# rollup-plugin-preprocessor +A Rollup plugin that adds a C-like preprocessor to Javascript. + +# Screenshots +![Very useful comparison image (this is a lie)](assets/comparison.png) + +# Documentation +## Options +This plugin exports a `preprocess()` plugin function that takes in an options object with the following syntax: +```js +{ + //a list of compile-time variables and their values (parsed to an AST, so all JS is valid) + //NOTE: due to this, strings require single quotes around them to parse to String + values: { + EXAMPLE: "'Hello, world!'", + IS_TRUE: 1, + WEBPACK: "demon.summon('modules/webpack')" + }, + //a list of comment transformations + transforms: { + //example: this deletes all comments that start with NONPROD in the output AST + NONPROD: (node, parent, idx) => {parent.comments = []} + } +} +``` +## Included Directives +This plugin currently includes 3, battle-tested, production-ready directives for any code you may want to use them in. +`#IF comptime_var` - If `comptime_var` evaluates to true, the code from it until the end is included in the output; otherwise, it is ignored. +`#IFNOT comptime_var` - If `comptime_var` evaluates to false, the code from it until the end is included in the output; otherwise, it is ignored. +`#END` - Ends an `#IF`/`#IFNOT` macro. + +# Commonly Asked Questions +## Why? +I prefer doing things like this before runtime with compile-time constants, instead of on runtime with JS variables. +## Are you insane? +Yeah, probably. +## Why not just use a Regex? +I'm just #special like that. +## Is this production ready? +No. \ No newline at end of file diff --git a/assets/comparison.png b/assets/comparison.png new file mode 100644 index 0000000..31c70c2 Binary files /dev/null and b/assets/comparison.png differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..9e11b90 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "rollup-plugin-preprocessor", + "version": "1.0.0", + "description": "A Rollup plugin that adds a C-like preprocessor to Javascript.", + "main": "src/main.js", + "type": "module", + "scripts": { + "test": "pushd test; rollup -c" + }, + "keywords": ["rollup", "rollup-plugin", "c", "preprocessor"], + "author": "Ruthenic", + "license": "WTFPL", + "dependencies": { + "estree-walker": "^3.0.1", + "recast": "^0.21.2" + }, + "peerDependencies": { + "rollup": "^2.78.1" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..f4943e6 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,85 @@ +lockfileVersion: 5.4 + +specifiers: + acorn: ^8.8.0 + estree-util-attach-comments: ^2.1.0 + estree-walker: ^3.0.1 + recast: ^0.21.2 + rollup: ^2.78.1 + +dependencies: + acorn: 8.8.0 + estree-util-attach-comments: 2.1.0 + estree-walker: 3.0.1 + recast: 0.21.2 + rollup: 2.78.1 + +packages: + + /@types/estree/1.0.0: + resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} + dev: false + + /acorn/8.8.0: + resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: false + + /ast-types/0.15.2: + resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} + engines: {node: '>=4'} + dependencies: + tslib: 2.4.0 + dev: false + + /esprima/4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /estree-util-attach-comments/2.1.0: + resolution: {integrity: sha512-rJz6I4L0GaXYtHpoMScgDIwM0/Vwbu5shbMeER596rB2D1EWF6+Gj0e0UKzJPZrpoOc87+Q2kgVFHfjAymIqmw==} + dependencies: + '@types/estree': 1.0.0 + dev: false + + /estree-walker/3.0.1: + resolution: {integrity: sha512-woY0RUD87WzMBUiZLx8NsYr23N5BKsOMZHhu2hoNRVh6NXGfoiT1KOL8G3UHlJAnEDGmfa5ubNA/AacfG+Kb0g==} + dev: false + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /recast/0.21.2: + resolution: {integrity: sha512-jUR1+NtaBQdKDqBJ+qxwMm5zR8aLSNh268EfgAbY+EP4wcNEWb6hZFhFeYjaYanwgDahx5t47CH8db7X2NfKdQ==} + engines: {node: '>= 4'} + dependencies: + ast-types: 0.15.2 + esprima: 4.0.1 + source-map: 0.6.1 + tslib: 2.4.0 + dev: false + + /rollup/2.78.1: + resolution: {integrity: sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: false + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false + + /tslib/2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + dev: false diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..6b2c2ee --- /dev/null +++ b/src/main.js @@ -0,0 +1,96 @@ +import { walk } from 'estree-walker' +import recast from 'recast' + +//my thoughts on this code are: na na na nani the fuck +export default function preprocess(opts) { + return { + name: "preprocess", + transform: { + order: 'pre', + handler(code) { + const state = { + failedIf: false + } + const parse = this.parse + + const comments = [] + const ast = recast.parse(code, { + parser: this + }) + walk(ast, { //WALK CST FOR COMPTIME_VARS + enter(node, parent, prop, index) { + if (parent?.type === "MemberExpression" && parent?.property === node) { + if (state.failedIf) { + delete parent[prop] + } + return //ignore childs of objects ie console.log + } else if (node.type === "Identifier") { + if (opts.values) { + for (const i in opts.values) { + if (i === node.name) { + console.log(`Found comp-time var ${i} with value ${opts.values[i]}`) + const res = eval(`(${opts.values[i]})`) + if (typeof res === "string") { + parent[prop] = [ parse(`('${res}')`).body[0].expression ] + } else { + parent[prop] = [ parse(`(${res})`).body[0].expression ] + } + return + } + } + } + } else if (node.comments) { + node.comments.forEach((cmt, cidx) => { + for (const i in opts.transforms) { + if (cmt.value.startsWith(i)) { + console.log(`Found comp-time comment transformation ${i}`) + opts.transforms[i](cmt, node, cidx) + return + } + } + if (cmt.value.toUpperCase().startsWith("#IF ")) { + const vari = cmt.value.substring(4) + node.comments = [] + if (opts.values[vari] === undefined) { + throw new Error("Cannot use undefined comp-time variable in #IF macro!") + } else { + const res = eval(`(${opts.values[vari]})`) + if (!!res) { + console.log("Broke into #IF macro") + } else { + console.log(`Unmet #IF for ${vari}`) + state.failedIf = true + } + } + } else if (cmt.value.toUpperCase().startsWith("#IFNOT ")) { + const vari = cmt.value.substring(7) + node.comments = [] + if (opts.values[vari] === undefined) { + throw new Error("Cannot use undefined comp-time variable in #IFNOT macro!") + } else { + const res = eval(`(${opts.values[vari]})`) + if (!res) { + console.log("Broke into #IFNOT macro") + } else { + console.log(`Unmet #IFNOT for ${vari} at Line ${cmt.loc.start.line}:${cmt.loc.start.column}`) + state.failedIf = true + } + } + } else if (cmt.value.toUpperCase().startsWith("#END")) { + console.log("Broke out of macro") + node.comments = [] + state.failedIf = false + } + }) + } else { + if (state.failedIf) { + delete parent[prop] + } + } + } + }) + return { code: recast.print(ast).code } + } + } + } +} diff --git a/test/out.js b/test/out.js new file mode 100644 index 0000000..21518b4 --- /dev/null +++ b/test/out.js @@ -0,0 +1,6 @@ +console.log("Hello, world!"); + +console.log("#IF works"); +console.log("#IFNOT works"); +console.log("Hello, world!"); +console.log(2); diff --git a/test/rollup.config.mjs b/test/rollup.config.mjs new file mode 100644 index 0000000..53a02ac --- /dev/null +++ b/test/rollup.config.mjs @@ -0,0 +1,21 @@ +import preprocess from "../src/main.js" + +export default { + input: "./test.js", + output: { + file: "out.js" + }, + plugins: [ + preprocess({ + values: { + "COMPTIME_STR": "'Hello, world!'", + "COMPTIME_NUM": 2, + "IS_TRUE": 1, + "IS_FALSE": 0 + }, + transforms: { + "NONPROD": (node, parent, idx) => {parent.comments = []} + } + }) + ] +} diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..06ee5aa --- /dev/null +++ b/test/test.js @@ -0,0 +1,18 @@ +console.log("Hello, world!") + +//#IF IS_TRUE +console.log("#IF works") +//#END +//#IF IS_FALSE +console.log("#IF does not work (ignore the last one if this shows up)") +//#END + +//#IfNoT IS_FALSE +console.log("#IFNOT works") +//#END +//#ifnot IS_TRUE +console.log("#IFNOT does not work (disregard prior)") +//#end + +console.log(COMPTIME_STR) +console.log(COMPTIME_NUM) \ No newline at end of file