Security News
npm Updates Search Experience with New Objective Sorting Options
npm has a revamped search experience with new, more transparent sorting options—Relevance, Downloads, Dependents, and Publish Date.
AtBuild is a programmable code generation tool for JavaScript. It lets you write JavaScript that writes JavaScript.
AtBuild is a programmable code generation tool for JavaScript. It lets you write JavaScript that writes JavaScript.
Use it for:
Try the playground: https://atbuild.vercel.app/bundle/date-formatter.tsb
There are two flavors of AtBuild.
.jsb
/.tsb
Atbuild Light preprocesses your JavaScript & TypeScript files by setting three conventions:
$(buildTimeCodeInHere)
will be run & replaced at buildtime (❤️ jQuery)// $$
will be moved to buildtime// $
with be moved to buildtimeinput.js
:
import { $ } from "atbuild";
// $$
const didRemoveBuildTimeCode = false;
// $$-
export const isRemoved = $(!didRemoveBuildTimeCode);
⌨️ atbuild ./input.js ./output.js
output.js
:
const isRemoved = true;
export { isRemoved };
Note: the import {$}
is there for convience so your editor doesn't get mad. Any function call starting with $
is assumed to be a build-time function.
Unlike other buildtime code generation tools, you can import
from node_modules
, and even import other modules in your codebase (so long as it runs without a window
object). The input is transformed using esbuild
.
input.js
:
import { $createDateFormatter } from "atbuild/demo/date-formatter"; // $
export const formatTime = $createDateFormatter("hh:mm:ss");
⌨️ atbuild ./input.js ./output.js
output.js
:
export const formatTime = function (date: Date) {
let formattedDate = "";
var hours = date.getUTCHours() % 12;
hours = hours || 12;
formattedDate += hours.toString(10).padStart(2, "0");
formattedDate += ":";
formattedDate += date.getUTCMinutes().toString(10).padStart(2, "0");
formattedDate += ":";
formattedDate += date.getUTCSeconds().toString(10).padStart(2, "0");
return formattedDate;
};
And it supports types.
For compatibility reasons, exporting build time code from JavaScript/TypeScript outside of the file is not supported. But, that's why there's Atbuild Full, which lets you write libraries for proceedurally generating code at build time.
Atbuild Full adds a few new keywords to JavaScript. It lets you evaluate & generate code at build time using JavaScript.
@build
: code contained inside @build
will be run at build-time instead of runtime@build
const yourBrowserDoesntKnowAboutThisCode = true;
@end
@run
: code contained inside @run
will be included at runtime.@run
console.log("This code will be included at runtime");
@end
@run
and @build
can be nested. @()
is like string interpolation but for generating code.@build
// This for loop isn't included in the runtime code.
for (let i = 0; i < 3;i++) {
@run
console.log("This code will be included at runtime @(i)");
@end
}
@end
And this is the output:
console.log("This code will be included at runtime 0");
console.log("This code will be included at runtime 1");
console.log("This code will be included at runtime 2");
@export function $FunctionNameGoesHere(arguments, in, here)
adds a build-time exported function that can be called from regular JavaScript/TypeScript files. Before it reaches the browser, the function call is replaced with the code generated from the function call.You write some of your JavaScript in .jsb
files, and by default, all the code in the file will be evaluated at runtime.
The code evaluated at buildtime is also JavaScript.
All of this works with your bundler, so you can import React components and generates type definitions.
// contrived-api-endpoint-codegenerator.jsb.
@build
import { kebabCase, startCase, toLower} from 'lodash';
const titleize = str => startCase(toLower(str));
const BASE_URL = `http://example.com`;
@end
type BaseType = {
id: number;
}
@build
for (let objectName of ["Post", "User", "Like", "PasswordResetToken"]) {
@run
export type @(objectName) = BaseType & {
object: "@(kebabCase(objectName))";
@build
switch(objectName) {
case "PasswordResetToken": {
@run
used: boolean;
expiry: Date;
@end
}
}
@end
}
export function build@(objectName)FromJSON(json: Object): @(objectName) {
return json;
}
export async function fetch@(objectName)ById(id: number): Promise<@(objectName)> {
@build
var base = BASE_URL + `/${kebabCase(objectName)}s/`;
@end
const body = (await fetch("@(base)" + id)).body()
const json = await body.json()
return build@(objectName)FromJSON(json);
}
@end
}
@end
After we run it through atbuild ./contrived-api-endpoint-codegenerator.jsb
, it becomes:
// contrived-api-endpoint-codegenerator.js
function buildPostFromJSON(json) {
return json;
}
async function fetchPostById(id) {
const body = (await fetch("http://example.com/posts/" + id)).body();
const json = await body.json();
return buildPostFromJSON(json);
}
function buildUserFromJSON(json) {
return json;
}
async function fetchUserById(id) {
const body = (await fetch("http://example.com/users/" + id)).body();
const json = await body.json();
return buildUserFromJSON(json);
}
function buildLikeFromJSON(json) {
return json;
}
async function fetchLikeById(id) {
const body = (await fetch("http://example.com/likes/" + id)).body();
const json = await body.json();
return buildLikeFromJSON(json);
}
function buildPasswordResetTokenFromJSON(json) {
return json;
}
async function fetchPasswordResetTokenById(id) {
const body = (
await fetch("http://example.com/password-reset-tokens/" + id)
).body();
const json = await body.json();
return buildPasswordResetTokenFromJSON(json);
}
export {
buildLikeFromJSON,
buildPasswordResetTokenFromJSON,
buildPostFromJSON,
buildUserFromJSON,
fetchLikeById,
fetchPasswordResetTokenById,
fetchPostById,
fetchUserById,
};
This also generates a contrived-api-endpoint-codegenerator.ts.d
file:
declare type BaseType = {
id: number;
};
export declare type Post = BaseType & {
object: "post";
};
export declare function buildPostFromJSON(json: Object): Post;
export declare function fetchPostById(id: number): Promise<Post>;
export declare type User = BaseType & {
object: "user";
};
export declare function buildUserFromJSON(json: Object): User;
export declare function fetchUserById(id: number): Promise<User>;
export declare type Like = BaseType & {
object: "like";
};
export declare function buildLikeFromJSON(json: Object): Like;
export declare function fetchLikeById(id: number): Promise<Like>;
export declare type PasswordResetToken = BaseType & {
object: "password-reset-token";
used: boolean;
expiry: Date;
};
export declare function buildPasswordResetTokenFromJSON(
json: Object
): PasswordResetToken;
export declare function fetchPasswordResetTokenById(
id: number
): Promise<PasswordResetToken>;
export {};
November 6th: New syntax for Atbuild Full, and a new parser to go with it.
October 30th, 2020: Added support for nested buildtime modules to export functions that are only available at buildtime. This allows you to write zero-runtime libraries.
October 30th, 2020: Added support for nested buildtime modules in the webpack-loader, so you can import jsb files from inside jsb files and it will work as expected (buildtime code is executed, runtime code is generated)
October 29th, 2020: Added support for bundling buildtime code in the webpack loader, meaning you can use the same syntax for buildtime code and runtime code. This also makes it easy to import runtime modules at buildtime. The webpack-loader uses esbuild for bundling the backend code.
October 28th, 2020: Extremely WIP VSCode extension.
October 28th, 2020: Added support for require
in buildtime code. Runtime code works like normal and is run through Babel or any other loaders you use. Buildtime code isn't run through babel, but this might be implemented later via webpack's Fixedthis._compilation_.createChildCompiler
, which would run buildtime and runtime code both through webpack.
Extremely fast native languages like Rust & C often use inline expansion and loop unrolling to move work from runtime to buildtime. For code that doesn't change much, this can be a massive performance improvement.
Unfortunately, since JavaScript is a dynamic language, that's not natively supported. High performance JavaScript libraries like ndarray and Kiwi resort to writing code inside code by adding strings together, which is hard for humans to read whats going on.
Nowadays, much of the JavaScript we write is already behind seven different compilers, so why not add another?
I wrote AtBuild because I needed to improve the performance for some parts of a game I'm building.
But, here are some other ways you could use this:
How is this different than Prepack?
Like AtBuild, Prepack inlines & prevaluates code. But, AtBuild lets you choose what code runs at runtime and what code runs at buildtime, and use that to generate code. Loops that conditionally add or remove runtime code are not possible with Prepack or with babel-plugin-codegen
.
With yarn:
yarn add atbuild
npm:
npm install atbuild
atbuild
has a small CLI you can use.
atbuild ./input.jsb
atbuild ./input.jsb ./output.js
atbuild ./input.jsb ./output.js --pretty --no-header
The recommended way to use AtBuild is through the Webpack loader
Buildtime code is run through a high performance bundler for you automatically, so you can write your buildtime code using the same modern JavaScript as the rest of your code. This also means you can import other modules, and those modules don't have to be .jsb
files - they can be any other file in your codebase (so long as it runs in Node after bundling).
Runtime code is passed through webpack as regular JavaScript – so you can still use babel-loader as normal.
// Webpack config
module.exports = {
// ...
module: {
// ...
rules: [
// ...
// AtBuild.js Webpack Loader
{
test: /\.(jsb|js|ts|tsx|jsx|@js)$/,
exclude: /node_modules/,
enforce: "pre",
use: [
{
loader: "atbuild/webpack-loader,
options: {
// Generate a .d.ts file automatically so your IDE can more easily interop with AtBuild files.
typescript: true,
}
},
]
},
],
}
// ...
}
This will be cleaned up & moved into a plugin eventually (such as next-with-atbuild
), however this is how I currently use AtBuild with Next.js:
// Figure out where next-babel-loader is hiding
const nextBabelLoaderContainer = config.module.rules.find((rule) => {
return (
(rule.use && rule.use.loader && rule.use.loader === "next-babel-loader") ||
(rule.use &&
rule.use.find((loader) => loader.loader === "next-babel-loader"))
);
});
if (nextBabelLoaderContainer) {
let loader;
if (nextBabelLoaderContainer.use.loader === "next-babel-loader") {
loader = nextBabelLoaderContainer.use;
} else {
loader = nextBabelLoaderContainer.use.find(
(loader) => loader.loader === "next-babel-loader"
);
}
config.module.rules.unshift({
test: /\.jsb$/,
use: [
// Pass the loader in before atbuild, so that atbuild runs first.g
loader,
{
// This is where the webpack loader is added.
loader: "atbuild/webpack-loader",
},
],
});
} else {
// Feel free to open an issue if you see this warning.
console.warn("Unable to activate AtBuild");
}
babel-plugin-codegen
makes it easy to run build scripts, but it gets tough if you want to do some things at buildtime and some other things at run-time for the same code.FAQs
AtBuild is a programmable code generation tool for JavaScript. It lets you write JavaScript that writes JavaScript.
We found that atbuild demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
npm has a revamped search experience with new, more transparent sorting options—Relevance, Downloads, Dependents, and Publish Date.
Security News
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.