Skip to content

Commit

Permalink
feat(macros): add macros (undocumented pre-release feature)
Browse files Browse the repository at this point in the history
This adds alpha support for
[babel-macros](https://github.com/kentcdodds/babel-macros)

ref: kentcdodds/babel-plugin-macros#1

Usage:

```javascript
import preval from 'babel-plugin-preval/macros'

const x = preval`module.exports = Math.random()`
```
  • Loading branch information
Kent C. Dodds committed Jul 7, 2017
1 parent e2cc1a5 commit 635a162
Show file tree
Hide file tree
Showing 8 changed files with 3,585 additions and 576 deletions.
3,893 changes: 3,375 additions & 518 deletions package-lock.json

Large diffs are not rendered by default.

24 changes: 6 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,21 @@
"commitmsg": "opt --in commit-msg --exec \"validate-commit-msg\"",
"precommit": "lint-staged && opt --in pre-commit --exec \"npm start validate\""
},
"files": [
"dist"
],
"keywords": [
"babel",
"babel-plugin",
"eval",
"precompile"
],
"files": ["dist"],
"keywords": ["babel", "babel-plugin", "eval", "precompile"],
"author": "Kent C. Dodds <kent@doddsfamily.us> (http://kentcdodds.com/)",
"license": "MIT",
"dependencies": {
"babel-core": "^6.25.0",
"babylon": "^6.17.4",
"require-from-string": "^1.2.1"
},
"devDependencies": {
"all-contributors-cli": "^4.3.0",
"ast-pretty-print": "^2.0.0",
"babel-cli": "^6.24.1",
"babel-core": "^6.25.0",
"babel-jest": "^20.0.3",
"babel-macros": "0.3.0",
"babel-plugin-tester": "^3.2.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-inline-environment-variables": "0.1.1",
Expand All @@ -50,17 +44,11 @@
"validate-commit-msg": "^2.12.1"
},
"lint-staged": {
"*.js": [
"prettier-eslint --write --print-width=80",
"git add"
]
"*.js": ["prettier-eslint --write --print-width=80", "git add"]
},
"jest": {
"testEnvironment": "node",
"testPathIgnorePatterns": [
"/node_modules/",
"/fixtures/"
],
"testPathIgnorePatterns": ["/node_modules/", "/fixtures/"],
"coverageThreshold": {
"global": {
"branches": 100,
Expand Down
46 changes: 46 additions & 0 deletions src/__tests__/__snapshots__/macros.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`1. as tag 1`] = `
import preval from '../macros'
const x = preval\`module.exports = require('./fixtures/compute-one')\`
↓ ↓ ↓ ↓ ↓ ↓
const x = 1;
`;

exports[`2. as function 1`] = `
const myPreval = require('../macros')
const x = myPreval(\`
module.exports = require('./fixtures/identity')({sayHi: () => 'hi'})
\`)
↓ ↓ ↓ ↓ ↓ ↓
const x = { "sayHi": function sayHi() {
return 'hi';
} };
`;

exports[`3. as jsx 1`] = `
const Preval = require('../macros')
const ui = (
<Preval>
const fs = require('fs')
module.exports = fs.readFileSync(require.resolve('./fixtures/fixture1.md'), 'utf8')
</Preval>
)
↓ ↓ ↓ ↓ ↓ ↓
const ui = <div>"# fixture\\n\\nThis is some file thing...\\n"</div>;
`;
73 changes: 73 additions & 0 deletions src/__tests__/macros.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import path from 'path'
import pluginTester from 'babel-plugin-tester'
import plugin from 'babel-macros'

const projectRoot = path.join(__dirname, '../../')

expect.addSnapshotSerializer({
print(val) {
return val.split(projectRoot).join('<PROJECT_ROOT>/')
},
test(val) {
return typeof val === 'string'
},
})

pluginTester({
plugin,
snapshot: true,
tests: withFilename([
{
title: 'as tag',
code: `
import preval from '../macros'
const x = preval\`module.exports = require('./fixtures/compute-one')\`
`,
},
{
title: 'as function',
code: `
const myPreval = require('../macros')
const x = myPreval(\`
module.exports = require('./fixtures/identity')({sayHi: () => 'hi'})
\`)
`,
},
{
title: 'as jsx',
code: `
const Preval = require('../macros')
const ui = (
<Preval>
const fs = require('fs')
module.exports = fs.readFileSync(require.resolve('./fixtures/fixture1.md'), 'utf8')
</Preval>
)
`,
},
]),
})

/*
* This adds the filename to each test so you can do require/import relative
* to this test file.
*/
function withFilename(tests) {
return tests.map(t => {
const test = {babelOptions: {filename: __filename}}
if (typeof t === 'string') {
test.code = t
} else {
Object.assign(test, t)
test.babelOptions.parserOpts = test.babelOptions.parserOpts || {}
}
Object.assign(test.babelOptions.parserOpts, {
// add the jsx plugin to all tests because why not?
plugins: ['jsx'],
})
return test
})
}
13 changes: 13 additions & 0 deletions src/get-replacement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const babel = require('babel-core')
const requireFromString = require('require-from-string')
const objectToAST = require('./object-to-ast')

module.exports = getReplacement

function getReplacement({string: stringToPreval, filename}) {
const {code: transpiled} = babel.transform(stringToPreval, {
filename,
})
const val = requireFromString(transpiled, filename)
return objectToAST(val)
}
45 changes: 5 additions & 40 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const p = require('path')
const babylon = require('babylon')
const requireFromString = require('require-from-string')
// const printAST = require('ast-pretty-print')
const getReplacement = require('./get-replacement')
const objectToAST = require('./object-to-ast')

module.exports = prevalPlugin

function prevalPlugin({types: t, template, transform, transformFromAst}) {
function prevalPlugin({types: t, template, transformFromAst}) {
const assignmentBuilder = template('const NAME = VALUE')
return {
name: 'preval',
Expand Down Expand Up @@ -47,9 +47,7 @@ function prevalPlugin({types: t, template, transform, transformFromAst}) {
}
const string = path.get('quasi').evaluate().value
if (!string) {
throw new Error(
'Unable to determine the value of your preval string',
)
throw new Error('Unable to determine the value of your preval string')
}
const replacement = getReplacement({string, filename})
path.replaceWith(replacement)
Expand Down Expand Up @@ -143,20 +141,6 @@ function prevalPlugin({types: t, template, transform, transformFromAst}) {
},
},
}

function getReplacement({string: stringToPreval, filename}) {
const {code: transpiled} = transform(stringToPreval, {
filename,
})
const val = requireFromString(transpiled, filename)
return objectToAST(val)
}
}

function objectToAST(object) {
const stringified = stringify(object)
const fileNode = babylon.parse(`var x = ${stringified}`)
return fileNode.program.body[0].declarations[0].init
}

function isPrevalComment(comment) {
Expand All @@ -167,25 +151,6 @@ function isPrevalComment(comment) {
)
}

function stringify(object) {
// if (typeof object === 'string') {
// return object
// }
return JSON.stringify(object, stringifyReplacer).replace(
/"__FUNCTION_START__(.*)__FUNCTION_END__"/g,
functionReplacer,
)
function stringifyReplacer(key, value) {
if (typeof value === 'function') {
return `__FUNCTION_START__${value.toString()}__FUNCTION_END__`
}
return value
}
function functionReplacer(match, p1) {
return p1.replace(/\\"/g, '"').replace(/\\n/g, '\n')
}
}

function looksLike(a, b) {
return (
a &&
Expand All @@ -203,7 +168,7 @@ function looksLike(a, b) {

function isPrimitive(val) {
// eslint-disable-next-line
return val == null || /^[sbn]/.test(typeof val);
return val == null || /^[sbn]/.test(typeof val)
}

/*
Expand Down
42 changes: 42 additions & 0 deletions src/macros.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// const printAST = require('ast-pretty-print')
const getReplacement = require('./get-replacement')

// this implements the babel-macros v0.2.0 API
module.exports = {asTag, asFunction, asJSX}

function asTag(quasiPath, {file: {opts: {filename}}}) {
const string = quasiPath.parentPath.get('quasi').evaluate().value
quasiPath.parentPath.replaceWith(
getReplacement({
string,
filename,
}),
)
}

function asFunction(argumentsPaths, {file: {opts: {filename}}}) {
const string = argumentsPaths[0].evaluate().value
argumentsPaths[0].parentPath.replaceWith(
getReplacement({
string,
filename,
}),
)
}

// eslint-disable-next-line no-unused-vars
function asJSX({attributes, children}, {file: {opts: {filename}}}) {
// It's a shame you cannot use evaluate() with JSX
const string = children[0].node.value
children[0].replaceWith(
getReplacement({
string,
filename,
}),
)
const {
parentPath: {node: {openingElement, closingElement}},
} = children[0]
openingElement.name.name = 'div'
closingElement.name.name = 'div'
}
25 changes: 25 additions & 0 deletions src/object-to-ast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const babylon = require('babylon')

module.exports = objectToAST

function objectToAST(object) {
const stringified = stringify(object)
const fileNode = babylon.parse(`var x = ${stringified}`)
return fileNode.program.body[0].declarations[0].init
}

function stringify(object) {
return JSON.stringify(object, stringifyReplacer).replace(
/"__FUNCTION_START__(.*)__FUNCTION_END__"/g,
functionReplacer,
)
function stringifyReplacer(key, value) {
if (typeof value === 'function') {
return `__FUNCTION_START__${value.toString()}__FUNCTION_END__`
}
return value
}
function functionReplacer(match, p1) {
return p1.replace(/\\"/g, '"').replace(/\\n/g, '\n')
}
}

0 comments on commit 635a162

Please sign in to comment.