Skip to content

mr-shabani/babel-plugin-as-macro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

babel-plugin-as-macro

Power of macro in javascript code.

Motivation

When I was using Next.js, I want to use the y18n module for internationalization. But y18n uses the file system(fs module) to save words and their meanings. Importing any module that uses the file system is prohibited by next.js. The first solution that I found, was preval module. But my need is more. I want to have a syntax as simple as :

import React from "react";
import /*as macro*/ y18n from "y18n";
{/*as macro*/{
	y18n = y18n(/* some config */);
	y18n.setLocal("en")
	var __ = y18n.__;
}}
export default function page() {
	return (
		<div>
			<p>{__("HELLO WORLD")}</p>
			<button>{y18n.__n("one button", "%d two button", 2)}</button>
		</div>
	);
}

Installation

You can install this module via npm.

npm install --save-dev babel-plugin-as-macro

Then, you must configure babel with .babelrc file.

{
	//...
	// "presets": ["next/babel"],  /* Add this line if you want to use with next.js */
	"plugin": [
		// other plugins that you want to
		// be executed before this plugin
		["as-macro",/*plugin options*/],
		// plugins that will be executed
		// after this plugin
	]
}

Usage

Macro Definition

There are three ways to define a macro. Because of the nature of macro that is not in any scope of your code, we force by default that macro definitions only be possible in the global scope.

Import Declaration

You can define macro by importing a module. You must place the comment block /*as macro*/ after import identifier.

import /*as macro*/ y18n from "y18n";
import /*as macro*/ X,{x1 as y1,x2 as y2} from "anyModule";

The import statement will be deleted from the code.

In the import statement you can use ECMAScript modules(don't use *.jsx) or node modules. We use esm module for importing ECMAScript modules.

Variable Declaration

You can define macro by defining variables. You must place the comment block /*as macro*/ after var identifier.

var /*as macro*/ y18n = require("y18n")(/*some config*/);
var /*as macro*/ __ = y18n.__ , str = "some string";

Variable declaration statement will be deleted from the code.

Block Statement

You can run any nodejs code in a macro-block like a macro definition. The syntax of macro-block is a block statement in the global that has a comment block /*as macro*/ at the first and a block statement after that.

{/*as macro*/{
// This block will be run at build time
}}

You can do anything possible in nodejs in a macro-block. Variables that are defined in macro-block with var will also be a macro.

{/*as macro*/{
	var y18n = require("y18n");
	y18n = y18n(/*some config*/);
	var __ = y18n.__;
	console.log("this will be printed at build time");
	let x = 1; /* this is not macro because this is
	a local variable that is only accessible in this block */
	while(x<10){
		console.log(x);
		x++;
	}
}}

The macro-block will be deleted from the code.

Macro Expression

Macro aims to execute some expression and replace the result in the code. Any expression that is a sequence of object memberships, function calls, and tag templates with a macro name as the main object will be caught as a macro expression.

macroName;
macroName`this macro is a function`;
macroName.property.method(some, argument).tagTemplateMethod`some string`
	.anotherProperty;

The macro expression will be run in nodejs, and the result will be replaced in the code. For replacement we use json-scriptify module.

Example 1:

var /*as macro*/ m = { x: 1 };
{
	/*as macro*/ {
		m.str = "some string";
	}
}
let obj = m;
↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓  
let obj = {
	x: 1,
	str: "some string"
};

Example 2:

{
	/*as macro*/ {
		var macroFunction = function computeAtCompileTime() {
			return "Result";
		};
	}
}
console.log(macroFunction());
let obj = macroFunction;
↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓  
console.log("Result");

let obj = function computeAtCompileTime() {
  return "Result";
};

Example 3:

All occurrences of a macro name, except variable definition, will be caught as macro expression. This can bite you.

var /*as macro*/ m1 = "this is a macro",
	m2 = "this is another macro";
var f = function double(m1) {
	let m2 = 2 * m1;
	return m2;
};
↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓  
var f = function double("this is a macro") {
  let m2 = 2 * "this is a macro";
  return "this is another macro";
};

Plugin Options

The plugin option is an object. You can access values in this object by info tool.

followScopes

followScopes is a boolean option that is false by default. If you set this option to true, you can use macro-block or define macro variables in any scope. Then, macros only available in that scope.

var /*as macro*/ m1 = "this is a macro",
	m2 = "this is another macro";
var f = function double(m1) {
	{
		/*as macro*/ {
			var localMacro = "this is local";
		}
	}
	let m2 = 2 * m1;
	var str = localMacro;
	return m2;
};
var globalString = localMacro;
↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓  
var f = function double(m1) {
	let m2 = 2 * m1;
	var str = "this is local";
	return m2;
};
var globalString = localMacro;

But, why we don't set followScopes to true by default? Because people may have mistakes like bellow.

if (bool) {
	var /*as macro*/ m = "bool is true";
} else {
	var /*as macro*/ m = "bool is false";
}
var string = m;
↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓   ↓  
if (bool) {
} else {
}
var string = "bool is false";

Regardless of variable bool, string is always "bool is false". Because both macro definitions are executed and the second definition overwrites first. Moreover, macros are executed at build time when bool has not been evaluated.

Tools

info

If you want to have some information about the file in which macros are defined and executed in, You can use info. You can import it from babel-plugin-as-macro/info in any module and use it. For example, you can have conf.js module that detects some config, like language, from the file path.

// conf.js
var info = require("babel-plugin-as-macro/info");
let someConfigurations = decideFrom(info.absolutePath);
module.exports = someConfigurations;

Then, you can use this module as macro in your code.

// main.js
import /*as macro*/ conf from "./conf.js";
/* do something with conf */

info is an object that will be filled by this plugin when the plugin start.

info = {
	options, // plugin options
	filename, // the name of file or "unknown" if has transformed from string
	root, // path to root of the project(directory of package.json)
	absoluteDir, // absolute path to the file directory
	relativeDir, // path to the directory relative to the root
	envName //process.env.BABEL_ENV || process.env.NODE_ENV || "development"
};

License

MIT

About

Power of macro in javascript code.

Resources

License

Stars

Watchers

Forks

Packages

No packages published