Skip to content

Commit 9d5f6cf

Browse files
authored
Create typegpu babel plugin (#804)
1 parent 077388e commit 9d5f6cf

File tree

17 files changed

+775
-437
lines changed

17 files changed

+775
-437
lines changed

apps/typegpu-docs/astro.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import sitemap from '@astrojs/sitemap';
55
import starlight from '@astrojs/starlight';
66
import tailwind from '@astrojs/tailwind';
77
import { defineConfig } from 'astro/config';
8-
import typegpu from 'rollup-plugin-typegpu';
98
import starlightBlog from 'starlight-blog';
109
import starlightTypeDoc, { typeDocSidebarGroup } from 'starlight-typedoc';
10+
import typegpu from 'unplugin-typegpu/rollup';
1111
import importRawRedirectPlugin from './vite-import-raw-redirect-plugin.mjs';
1212

1313
/**

apps/typegpu-docs/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"@astrojs/starlight": "^0.25.2",
1616
"@astrojs/starlight-tailwind": "^2.0.3",
1717
"@astrojs/tailwind": "^5.1.0",
18-
"@babel/standalone": "^7.24.7",
18+
"@babel/standalone": "^7.26.6",
1919
"@monaco-editor/react": "^4.6.0",
2020
"@radix-ui/react-select": "^2.1.1",
2121
"@radix-ui/react-slider": "^1.2.0",
@@ -34,7 +34,7 @@
3434
"react": "^18.3.1",
3535
"react-dom": "^18.3.1",
3636
"remeda": "^2.3.0",
37-
"rollup-plugin-typegpu": "workspace:*",
37+
"unplugin-typegpu": "workspace:*",
3838
"sharp": "^0.32.5",
3939
"starlight-blog": "^0.12.0",
4040
"starlight-typedoc": "^0.17.0",
@@ -48,7 +48,7 @@
4848
"zod": "^3.23.8"
4949
},
5050
"devDependencies": {
51-
"@types/babel__standalone": "^7.1.7",
51+
"@types/babel__standalone": "^7.1.9",
5252
"@types/babel__template": "^7.4.4",
5353
"@types/babel__traverse": "^7.20.6",
5454
"@webgpu/types": "^0.1.43",

apps/typegpu-docs/src/utils/examples/exampleRunner.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,19 @@ const staticToDynamicImports = {
5454
const wildCard = imports.wildCard;
5555
const nonWildCard = imports.nonWildCard;
5656

57-
path.replaceWith(
58-
template.program.ast(
59-
`
60-
${wildCard?.length ? `const ${wildCard[0][1]} = await _import('${moduleName}');` : ''}
61-
${nonWildCard?.length ? `const { ${nonWildCard.map((imp) => (imp[0] === imp[1] ? imp[0] : `${imp[0]}: ${imp[1]}`)).join(',')} } = await _import('${moduleName}');` : ''}
62-
`,
63-
),
57+
path.replaceWithMultiple(
58+
[
59+
wildCard?.length
60+
? [
61+
template.statement`const ${wildCard[0][1]} = await _import('${moduleName}');`(),
62+
]
63+
: [],
64+
nonWildCard?.length
65+
? [
66+
template.statement`const { ${nonWildCard.map((imp) => (imp[0] === imp[1] ? imp[0] : `${imp[0]}: ${imp[1]}`)).join(',')} } = await _import('${moduleName}');`(),
67+
]
68+
: [],
69+
].flat(),
6470
);
6571
},
6672
} satisfies TraverseOptions,
@@ -76,24 +82,21 @@ const exportedOptionsToExampleControls = () => {
7682

7783
if (declaration?.type === 'VariableDeclaration') {
7884
const init = declaration.declarations[0].init;
85+
7986
if (init) {
80-
path.replaceWith(
81-
template.program.ast(
82-
`import { addParameters } from '@typegpu/example-toolkit';
83-
addParameters(${code.slice(init.start ?? 0, init.end ?? 0)});`,
84-
),
85-
);
87+
path.replaceWithMultiple([
88+
template.statement`import { addParameters } from '@typegpu/example-toolkit'`(),
89+
template.statement`addParameters(${code.slice(init.start ?? 0, init.end ?? 0)});`(),
90+
]);
8691
}
8792
}
8893

8994
if (declaration?.type === 'FunctionDeclaration') {
9095
const body = declaration.body;
91-
path.replaceWith(
92-
template.program.ast(
93-
`import { onCleanup } from '@typegpu/example-toolkit';
94-
onCleanup(() => ${code.slice(body.start ?? 0, body.end ?? 0)});`,
95-
),
96-
);
96+
path.replaceWithMultiple([
97+
template.statement`import { onCleanup } from '@typegpu/example-toolkit'`(),
98+
template.statement`onCleanup(() => ${code.slice(body.start ?? 0, body.end ?? 0)});`(),
99+
]);
97100
}
98101
},
99102
} satisfies TraverseOptions,

packages/rollup-plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# rollup-plugin-typegpu
44

5-
🚧 **Under Construction** 🚧 - [GitHub](https://github.com/software-mansion/TypeGPU/tree/main/packages/rollup-plugin)
5+
⚠️ **This package is deprecated, please use `unplugin-typegpu` instead.** ⚠️
66

77
</div>
88

packages/rollup-plugin/package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,10 @@
5050
"prepare-package": "tgpu-dev-cli prepack"
5151
},
5252
"dependencies": {
53-
"tinyest-for-wgsl": "workspace:~0.1.0-alpha.0",
54-
"estree-walker": "^3.0.3",
55-
"magic-string": "^0.30.11"
53+
"unplugin-typegpu": "workspace:^0.0.0"
5654
},
5755
"devDependencies": {
5856
"@typegpu/tgpu-dev-cli": "workspace:*",
59-
"acorn": "^8.12.1",
6057
"rollup": "~4.12.0",
6158
"tsup": "^8.0.2",
6259
"typescript": "^5.3.3"

packages/rollup-plugin/src/index.ts

Lines changed: 3 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -1,187 +1,4 @@
1-
import type { AnyNode, CallExpression } from 'acorn';
2-
import { walk } from 'estree-walker';
3-
import MagicString from 'magic-string';
4-
import type { Plugin, SourceMap } from 'rollup';
5-
import { transpileFn } from 'tinyest-for-wgsl';
1+
import rollupPlugin from 'unplugin-typegpu/rollup';
2+
export { type TypegpuPluginOptions } from 'unplugin-typegpu';
63

7-
const typegpuImportRegex = /import.*from\s*['"]typegpu.*['"]/g;
8-
const typegpuDynamicImportRegex = /import\s*\(\s*['"]\s*typegpu.*['"]/g;
9-
const typegpuRequireRegex = /require\s*\(\s*['"]\s*typegpu.*['"]\s*\)/g;
10-
11-
type Context = {
12-
/**
13-
* How the `tgpu` object is used in code. Since it can be aliased, we
14-
* need to catch that and act accordingly.
15-
*/
16-
tgpuAliases: Set<string>;
17-
};
18-
19-
type TgslFunctionDef = {
20-
varDecl: CallExpression;
21-
implementation: AnyNode;
22-
};
23-
24-
function embedJSON(jsValue: unknown) {
25-
return JSON.stringify(jsValue)
26-
.replace(/\u2028/g, '\\u2028')
27-
.replace(/\u2029/g, '\\u2029');
28-
}
29-
30-
function gatherTgpuAliases(ctx: Context, node: AnyNode) {
31-
if (node.type === 'ImportDeclaration') {
32-
if (node.source.value === 'typegpu') {
33-
for (const spec of node.specifiers) {
34-
if (
35-
// The default export of 'typegpu' is the `tgpu` object.
36-
spec.type === 'ImportDefaultSpecifier' ||
37-
// Aliasing 'tgpu' while importing, e.g. import { tgpu as t } from 'typegpu';
38-
(spec.type === 'ImportSpecifier' &&
39-
spec.imported.type === 'Identifier' &&
40-
spec.imported.name === 'tgpu')
41-
) {
42-
ctx.tgpuAliases.add(spec.local.name);
43-
} else if (spec.type === 'ImportNamespaceSpecifier') {
44-
// Importing everything, e.g. import * as t from 'typegpu';
45-
ctx.tgpuAliases.add(`${spec.local.name}.tgpu`);
46-
}
47-
}
48-
}
49-
}
50-
}
51-
52-
/**
53-
* Checks if `node` is an alias for the 'tgpu' object, traditionally
54-
* available via `import tgpu from 'typegpu'`.
55-
*/
56-
function isTgpu(ctx: Context, node: AnyNode): boolean {
57-
let path = '';
58-
59-
let tail = node;
60-
while (true) {
61-
if (tail.type === 'MemberExpression') {
62-
if (tail.property.type !== 'Identifier') {
63-
// Not handling computed expressions.
64-
break;
65-
}
66-
67-
path = path ? `${tail.property.name}.${path}` : tail.property.name;
68-
tail = tail.object;
69-
} else if (tail.type === 'Identifier') {
70-
path = path ? `${tail.name}.${path}` : tail.name;
71-
break;
72-
} else {
73-
break;
74-
}
75-
}
76-
77-
return ctx.tgpuAliases.has(path);
78-
}
79-
80-
export interface TypegpuPluginOptions {
81-
include?: 'all' | RegExp[];
82-
}
83-
84-
export interface TypegpuPlugin {
85-
name: 'rollup-plugin-typegpu';
86-
transform(
87-
code: string,
88-
id: string,
89-
): { code: string; map: SourceMap } | undefined;
90-
}
91-
92-
export default function typegpu(options?: TypegpuPluginOptions): TypegpuPlugin {
93-
return {
94-
name: 'rollup-plugin-typegpu' as const,
95-
transform(code, id) {
96-
if (!options?.include) {
97-
if (
98-
!typegpuImportRegex.test(code) &&
99-
!typegpuRequireRegex.test(code) &&
100-
!typegpuDynamicImportRegex.test(code)
101-
) {
102-
// No imports to `typegpu` or its sub modules, exiting early.
103-
return;
104-
}
105-
} else if (
106-
options.include !== 'all' &&
107-
!options.include.some((pattern) => pattern.test(id))
108-
) {
109-
return;
110-
}
111-
112-
const ctx: Context = {
113-
tgpuAliases: new Set(['tgpu']),
114-
};
115-
116-
const ast = this.parse(code, {
117-
allowReturnOutsideFunction: true,
118-
});
119-
120-
const tgslFunctionDefs: TgslFunctionDef[] = [];
121-
122-
walk(ast, {
123-
enter(_node, _parent, prop, index) {
124-
const node = _node as AnyNode;
125-
126-
gatherTgpuAliases(ctx, node);
127-
128-
if (node.type === 'CallExpression') {
129-
if (
130-
node.callee.type === 'MemberExpression' &&
131-
node.arguments.length === 1 &&
132-
node.callee.property.type === 'Identifier' &&
133-
((node.callee.property.name === 'procedure' &&
134-
isTgpu(ctx, node.callee.object)) ||
135-
// Assuming that every call to `.does` is related to TypeGPU
136-
// because shells can be created separately from calls to `tgpu`,
137-
// making it hard to detect.
138-
node.callee.property.name === 'does')
139-
) {
140-
const implementation = node.arguments[0];
141-
142-
if (
143-
implementation &&
144-
!(implementation.type === 'TemplateLiteral') &&
145-
!(implementation.type === 'Literal')
146-
) {
147-
tgslFunctionDefs.push({
148-
varDecl: node,
149-
implementation,
150-
});
151-
}
152-
}
153-
}
154-
},
155-
});
156-
157-
const magicString = new MagicString(code);
158-
159-
for (const expr of tgslFunctionDefs) {
160-
const { argNames, body, externalNames } = transpileFn(
161-
expr.implementation,
162-
);
163-
164-
// Wrap the implementation in a call to `tgpu.__assignAst` to associate the AST with the implementation.
165-
magicString.appendLeft(expr.implementation.start, 'tgpu.__assignAst(');
166-
magicString.appendRight(
167-
expr.implementation.end,
168-
`, ${embedJSON({ argNames, body, externalNames })}`,
169-
);
170-
171-
if (externalNames.length > 0) {
172-
magicString.appendRight(
173-
expr.implementation.end,
174-
`, {${externalNames.join(', ')}})`,
175-
);
176-
} else {
177-
magicString.appendRight(expr.implementation.end, ', undefined)');
178-
}
179-
}
180-
181-
return {
182-
code: magicString.toString(),
183-
map: magicString.generateMap(),
184-
};
185-
},
186-
} satisfies Plugin;
187-
}
4+
export default rollupPlugin;

packages/tinyest-for-wgsl/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"tsup": "^8.0.2",
5959
"typescript": "^5.3.3",
6060
"acorn": "^8.12.1",
61+
"@babel/types": "7.26.5",
6162
"@typegpu/tgpu-dev-cli": "workspace:*"
6263
},
6364
"packageManager": "[email protected]+sha256.691fe176eea9a8a80df20e4976f3dfb44a04841ceb885638fe2a26174f81e65e",

0 commit comments

Comments
 (0)