├── .editorconfig
├── .gitattributes
├── .github
├── FUNDING.yml
├── renovate.json5
└── workflows
│ ├── release.yml
│ └── unit-test.yml
├── .gitignore
├── .npmrc
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── eslint.config.js
├── examples
├── esbuild
│ ├── build.ts
│ ├── dev.ts
│ ├── package.json
│ ├── src
│ │ ├── macros.ts
│ │ └── main.ts
│ └── tsconfig.json
└── vite
│ ├── .gitignore
│ ├── index.html
│ ├── package.json
│ ├── src
│ ├── macros.ts
│ ├── main.ts
│ └── vite-env.d.ts
│ ├── tsconfig.json
│ └── vite.config.ts
├── jsr.json
├── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── src
├── api.ts
├── core
│ ├── define.ts
│ ├── index.ts
│ └── options.ts
├── esbuild.ts
├── index.ts
├── rolldown.ts
├── rollup.ts
├── rspack.ts
├── vite.ts
└── webpack.ts
├── tests
├── __snapshots__
│ └── fixtures.test.ts.snap
├── define.test.ts
├── fixtures.test.ts
└── fixtures
│ ├── args.js
│ ├── basic.js
│ ├── css.js
│ ├── ctx.js
│ ├── dedent.js
│ ├── error-assert.js
│ ├── import-namespace.js
│ ├── macros
│ ├── args.ts
│ ├── css.ts
│ ├── ctx.ts
│ ├── inc.js
│ ├── nested-object.ts
│ ├── promise.ts
│ ├── rand.js
│ ├── string.js
│ └── var.ts
│ ├── nested-object.js
│ ├── node-builtin.js
│ ├── non-macros.js
│ ├── promise.js
│ └── typescript.ts
├── tsconfig.json
└── tsdown.config.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 2
5 | end_of_line = lf
6 | insert_final_newline = true
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: sxzz
2 |
--------------------------------------------------------------------------------
/.github/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | extends: ['github>sxzz/renovate-config'],
3 | automerge: true,
4 | }
5 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | permissions:
4 | contents: write
5 |
6 | on:
7 | push:
8 | tags:
9 | - 'v*'
10 |
11 | jobs:
12 | release:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | with:
17 | fetch-depth: 0
18 |
19 | - name: Set node
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: lts/*
23 |
24 | - run: npx changelogithub
25 | env:
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27 |
28 | publish-jsr:
29 | runs-on: ubuntu-latest
30 |
31 | permissions:
32 | contents: read
33 | id-token: write
34 |
35 | steps:
36 | - uses: actions/checkout@v4
37 |
38 | - name: Install pnpm
39 | uses: pnpm/action-setup@v4.1.0
40 |
41 | - name: Set node
42 | uses: actions/setup-node@v4
43 | with:
44 | node-version: lts/*
45 | cache: pnpm
46 |
47 | - name: Install
48 | run: pnpm i
49 |
50 | - name: Publish package
51 | run: npx jsr publish
52 |
--------------------------------------------------------------------------------
/.github/workflows/unit-test.yml:
--------------------------------------------------------------------------------
1 | name: Unit Test
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | lint:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 |
16 | - name: Install pnpm
17 | uses: pnpm/action-setup@v4.1.0
18 |
19 | - name: Set node
20 | uses: actions/setup-node@v4
21 | with:
22 | node-version: lts/*
23 | cache: pnpm
24 |
25 | - name: Install
26 | run: pnpm i
27 |
28 | - name: Lint
29 | run: pnpm lint
30 |
31 | - name: Typecheck
32 | run: pnpm typecheck
33 |
34 | test:
35 | runs-on: ${{ matrix.os }}
36 |
37 | strategy:
38 | matrix:
39 | os: [ubuntu-latest, windows-latest]
40 | node: [18, 20, 22]
41 | fail-fast: false
42 |
43 | steps:
44 | - name: Checkout
45 | uses: actions/checkout@v4
46 |
47 | - name: Install pnpm
48 | uses: pnpm/action-setup@v4.1.0
49 |
50 | - name: Set node ${{ matrix.node }}
51 | uses: actions/setup-node@v4
52 | with:
53 | node-version: ${{ matrix.node }}
54 | cache: pnpm
55 |
56 | - name: Install
57 | run: pnpm i
58 |
59 | - name: Build
60 | run: pnpm run build
61 |
62 | - name: Test
63 | run: pnpm run test
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | *.log
5 | .vercel
6 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | ignore-workspace-root-check = true
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2023-PRESENT 三咲智子 (https://github.com/sxzz)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # unplugin-macros [](https://npmjs.com/package/unplugin-macros) [](https://jsr.io/@unplugin/macros)
2 |
3 | [](https://github.com/unplugin/unplugin-macros/actions/workflows/unit-test.yml)
4 |
5 | > Macros are a mechanism for running JavaScript functions at bundle-time.
6 | > The value returned from these functions or variables are directly inlined into your bundle.
7 |
8 | ## Installation
9 |
10 | ```bash
11 | # npm
12 | npm i -D unplugin-macros
13 |
14 | # jsr
15 | npx jsr add -D @unplugin/macros
16 | ```
17 |
18 |
19 | Vite
20 |
21 | ```ts
22 | // vite.config.ts
23 | import Macros from 'unplugin-macros/vite'
24 |
25 | export default defineConfig({
26 | plugins: [Macros()],
27 | })
28 | ```
29 |
30 |
31 |
32 |
33 | Rollup
34 |
35 | ```ts
36 | // rollup.config.js
37 | import Macros from 'unplugin-macros/rollup'
38 |
39 | export default {
40 | plugins: [Macros()],
41 | }
42 | ```
43 |
44 |
45 |
46 |
47 | esbuild
48 |
49 | Requires esbuild >= 0.15
50 |
51 | ```ts
52 | // esbuild.config.js
53 | import { build } from 'esbuild'
54 |
55 | build({
56 | plugins: [require('unplugin-macros/esbuild')()],
57 | })
58 | ```
59 |
60 |
61 |
62 |
63 | Webpack
64 |
65 | ```ts
66 | // webpack.config.js
67 | module.exports = {
68 | /* ... */
69 | plugins: [require('unplugin-macros/webpack')()],
70 | }
71 | ```
72 |
73 |
74 |
75 | ## Usage
76 |
77 | ```js
78 | // main.js
79 | import { buildTime, getRandom } from './macros' with { type: 'macro' }
80 |
81 | getRandom() // Will be replaced with a random number at build time
82 | buildTime // Will be replaced with the timestamp at the build time
83 | ```
84 |
85 | ```js
86 | // macros.js
87 | export function getRandom() {
88 | return Math.random()
89 | }
90 | export const buildTime = Date.now()
91 | ```
92 |
93 | See more in [Bun Macros](https://bun.sh/blog/bun-macros).
94 |
95 | ### TypeScript
96 |
97 | Import Attributes syntax is supported in TypeScript 5.3 and above.
98 |
99 | ### ESLint
100 |
101 | Import Attributes syntax is supported in ESLint v9.14.0.
102 |
103 | ## Options
104 |
105 | Refer to [docs](https://jsr.io/@unplugin/macros/doc/api/~/Options).
106 |
107 | ## Thanks
108 |
109 | Thanks to [Bun Macros](https://bun.sh/blog/bun-macros).
110 |
111 | ## Sponsors
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | ## License
120 |
121 | [MIT](./LICENSE) License © 2023-PRESENT [三咲智子](https://github.com/sxzz)
122 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import parser from '@babel/eslint-parser'
2 | import { GLOB_JS, sxzz } from '@sxzz/eslint-config'
3 |
4 | export default sxzz({
5 | files: [GLOB_JS],
6 | languageOptions: {
7 | parser,
8 | parserOptions: {
9 | requireConfigFile: false,
10 | babelOptions: {
11 | parserOpts: {
12 | plugins: ['importAttributes'],
13 | },
14 | },
15 | },
16 | },
17 | })
18 |
--------------------------------------------------------------------------------
/examples/esbuild/build.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { build } from 'esbuild'
4 | import Macros from '../../src/esbuild'
5 |
6 | await build({
7 | entryPoints: ['src/main.ts'],
8 | bundle: true,
9 | outfile: 'dist/main.js',
10 | plugins: [Macros()],
11 | format: 'esm',
12 | })
13 |
14 | console.log('Success')
15 |
--------------------------------------------------------------------------------
/examples/esbuild/dev.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { context } from 'esbuild'
4 | import Macros from '../../src/esbuild'
5 |
6 | const ctx = await context({
7 | entryPoints: ['src/main.ts'],
8 | bundle: true,
9 | outdir: 'dist',
10 | plugins: [Macros()],
11 | format: 'esm',
12 | })
13 |
14 | const { hosts, port } = await ctx.serve({
15 | servedir: 'dist',
16 | })
17 |
18 | console.log(`http://${hosts[0]}:${port}`)
19 |
--------------------------------------------------------------------------------
/examples/esbuild/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "type": "module",
4 | "scripts": {
5 | "build": "tsx build.ts",
6 | "watch": "tsx watch build.ts",
7 | "dev": "tsx watch dev.ts"
8 | },
9 | "devDependencies": {
10 | "esbuild": "^0.25.4",
11 | "tsx": "^4.19.4"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/examples/esbuild/src/macros.ts:
--------------------------------------------------------------------------------
1 | export function getStartupTime() {
2 | return Date.now()
3 | }
4 |
5 | export function rand() {
6 | return Math.random()
7 | }
8 |
--------------------------------------------------------------------------------
/examples/esbuild/src/main.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { getStartupTime, rand } from './macros' with { type: 'macro' }
4 |
5 | console.log('Hello, world!')
6 | console.log('startup time', getStartupTime())
7 | console.log('rand', rand())
8 |
9 | export interface Test {
10 | foo?: string
11 | }
12 |
--------------------------------------------------------------------------------
/examples/esbuild/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
5 | "useDefineForClassFields": true,
6 | "module": "ESNext",
7 |
8 | /* Bundler mode */
9 | "moduleResolution": "bundler",
10 | "resolveJsonModule": true,
11 | "allowImportingTsExtensions": true,
12 |
13 | /* Linting */
14 | "strict": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "noEmit": true,
19 | "isolatedModules": true,
20 | "skipLibCheck": true
21 | },
22 | "include": ["src", "*"]
23 | }
24 |
--------------------------------------------------------------------------------
/examples/vite/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/vite/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "unplugin-macros": "workspace:*",
13 | "vite": "^6.1.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples/vite/src/macros.ts:
--------------------------------------------------------------------------------
1 | export function getStartupTime() {
2 | return Date.now()
3 | }
4 |
5 | export function rand() {
6 | return Math.random()
7 | }
8 |
--------------------------------------------------------------------------------
/examples/vite/src/main.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { getStartupTime, rand } from './macros' with { type: 'macro' }
4 |
5 | console.log('Hello, world!')
6 | console.log('startup time', getStartupTime())
7 | console.log('rand', rand())
8 |
9 | if (import.meta.hot) {
10 | import.meta.hot.accept()
11 | }
12 |
--------------------------------------------------------------------------------
/examples/vite/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/examples/vite/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
5 | "useDefineForClassFields": true,
6 | "module": "ESNext",
7 |
8 | /* Bundler mode */
9 | "moduleResolution": "bundler",
10 | "resolveJsonModule": true,
11 | "allowImportingTsExtensions": true,
12 |
13 | /* Linting */
14 | "strict": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "noUnusedLocals": true,
17 | "noUnusedParameters": true,
18 | "noEmit": true,
19 | "isolatedModules": true,
20 | "skipLibCheck": true
21 | },
22 | "include": ["src", "*"]
23 | }
24 |
--------------------------------------------------------------------------------
/examples/vite/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import Macros from '../../src/vite'
3 |
4 | export default defineConfig({
5 | clearScreen: false,
6 | plugins: [Macros()],
7 | })
8 |
--------------------------------------------------------------------------------
/jsr.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@unplugin/macros",
3 | "version": "0.17.0",
4 | "exports": {
5 | "./index": "./src/index.ts",
6 | "./api": "./src/api.ts",
7 | "./esbuild": "./src/esbuild.ts",
8 | "./rollup": "./src/rollup.ts",
9 | "./rolldown": "./src/rolldown.ts",
10 | "./vite": "./src/vite.ts",
11 | "./webpack": "./src/webpack.ts",
12 | "./rspack": "./src/rspack.ts"
13 | },
14 | "publish": {
15 | "include": [
16 | "src",
17 | "package.json",
18 | "jsr.json",
19 | "README.md",
20 | "LICENSE"
21 | ]
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "unplugin-macros",
3 | "version": "0.17.0",
4 | "packageManager": "pnpm@10.12.4",
5 | "description": "Macros for bundlers.",
6 | "type": "module",
7 | "keywords": [
8 | "unplugin",
9 | "rollup",
10 | "vite",
11 | "esbuild",
12 | "webpack"
13 | ],
14 | "license": "MIT",
15 | "homepage": "https://github.com/unplugin/unplugin-macros#readme",
16 | "bugs": {
17 | "url": "https://github.com/unplugin/unplugin-macros/issues"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/unplugin/unplugin-macros.git"
22 | },
23 | "author": "三咲智子 Kevin Deng ",
24 | "funding": "https://github.com/sponsors/sxzz",
25 | "files": [
26 | "dist"
27 | ],
28 | "main": "./dist/index.js",
29 | "module": "./dist/index.js",
30 | "types": "./dist/index.d.ts",
31 | "exports": {
32 | ".": "./dist/index.js",
33 | "./api": "./dist/api.js",
34 | "./vite": "./dist/vite.js",
35 | "./webpack": "./dist/webpack.js",
36 | "./rspack": "./dist/rspack.js",
37 | "./rollup": "./dist/rollup.js",
38 | "./rolldown": "./dist/rolldown.js",
39 | "./esbuild": "./dist/esbuild.js",
40 | "./*": "./*"
41 | },
42 | "typesVersions": {
43 | "*": {
44 | "*": [
45 | "./dist/*",
46 | "./*"
47 | ]
48 | }
49 | },
50 | "publishConfig": {
51 | "access": "public"
52 | },
53 | "scripts": {
54 | "lint": "eslint .",
55 | "lint:fix": "pnpm run lint --fix",
56 | "build": "tsdown",
57 | "dev": "tsdown --watch",
58 | "test": "vitest",
59 | "typecheck": "tsc --noEmit",
60 | "format": "prettier --cache --write .",
61 | "release": "bumpp && pnpm publish",
62 | "prepublishOnly": "pnpm run build"
63 | },
64 | "dependencies": {
65 | "ast-kit": "^2.1.1",
66 | "magic-string-ast": "^1.0.0",
67 | "unplugin": "^2.3.5",
68 | "vite": "^7.0.2",
69 | "vite-node": "^3.2.4"
70 | },
71 | "devDependencies": {
72 | "@babel/eslint-parser": "^7.28.0",
73 | "@babel/types": "^7.28.0",
74 | "@sxzz/eslint-config": "^7.0.4",
75 | "@sxzz/prettier-config": "^2.2.3",
76 | "@sxzz/test-utils": "^0.5.6",
77 | "@types/dedent": "^0.7.2",
78 | "@types/node": "^22.16.0",
79 | "bumpp": "^10.2.0",
80 | "dedent": "^1.6.0",
81 | "eslint": "^9.30.1",
82 | "fast-glob": "^3.3.3",
83 | "prettier": "^3.6.2",
84 | "rollup": "^4.44.2",
85 | "tsdown": "^0.12.9",
86 | "tsx": "^4.20.3",
87 | "typescript": "^5.8.3",
88 | "vitest": "^3.2.4",
89 | "vue": "^3.5.17"
90 | },
91 | "engines": {
92 | "node": ">=20.18.0"
93 | },
94 | "prettier": "@sxzz/prettier-config"
95 | }
96 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - examples/*
3 |
--------------------------------------------------------------------------------
/src/api.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for exposing the core API.
3 | *
4 | * @module
5 | */
6 |
7 | export * from './core'
8 |
--------------------------------------------------------------------------------
/src/core/define.ts:
--------------------------------------------------------------------------------
1 | import type { MacroContext } from './index'
2 |
3 | /**
4 | * A TypeScript helper function that defines a macro.
5 | *
6 | * @param fn - The function that represents the macro.
7 | * @returns A function that can be called with the macro arguments.
8 | */
9 | export function defineMacro(
10 | fn: (this: MacroContext, ...args: Args) => Return,
11 | ): (...args: Args) => Return {
12 | return fn
13 | }
14 |
--------------------------------------------------------------------------------
/src/core/index.ts:
--------------------------------------------------------------------------------
1 | import { builtinModules } from 'node:module'
2 | import {
3 | attachScopes,
4 | babelParse,
5 | getLang,
6 | isLiteralType,
7 | isReferenced,
8 | isTypeOf,
9 | resolveIdentifier,
10 | resolveLiteral,
11 | resolveObjectKey,
12 | TS_NODE_TYPES,
13 | walkAST,
14 | walkImportDeclaration,
15 | type ImportBinding,
16 | type WithScope,
17 | } from 'ast-kit'
18 | import { generateTransform, MagicStringAST } from 'magic-string-ast'
19 | import type { ImportAttribute, Node } from '@babel/types'
20 | import type { UnpluginBuildContext, UnpluginContext } from 'unplugin'
21 | import type { ViteNodeRunner } from 'vite-node/client'
22 |
23 | export * from './options'
24 | export * from './define'
25 |
26 | /**
27 | * Represents the context object passed to macros.
28 | */
29 | export interface MacroContext {
30 | id: string
31 | source: string
32 | emitFile: UnpluginBuildContext['emitFile']
33 | /**
34 | * **Use with caution.**
35 | *
36 | * This is an experimental feature and may be changed at any time.
37 | */
38 | unpluginContext: UnpluginBuildContext & UnpluginContext
39 | }
40 |
41 | interface MacroBase {
42 | node: Node
43 | id: string[]
44 | isAwait: boolean
45 | }
46 | interface CallMacro extends MacroBase {
47 | type: 'call'
48 | args: any[]
49 | }
50 | interface IdentifierMacro extends MacroBase {
51 | type: 'identifier'
52 | }
53 | type Macro = CallMacro | IdentifierMacro
54 |
55 | /**
56 | * Transforms macros in the given source code.
57 | * @param param0 - The transformation options.
58 | * @param param0.source - The source code to transform.
59 | * @param param0.id - The filename of the source file.
60 | * @param param0.unpluginContext - The unplugin context.
61 | * @param param0.getRunner - A function to get the ViteNodeRunner instance.
62 | * @param param0.deps - The dependencies of the source file.
63 | * @param param0.attrs - The import attributes to match.
64 | * @returns The transformed code and source map, or undefined if no macros were found.
65 | */
66 | export async function transformMacros({
67 | source,
68 | id,
69 | unpluginContext,
70 | getRunner,
71 | deps,
72 | attrs,
73 | }: {
74 | id: string
75 | source: string
76 | unpluginContext: UnpluginBuildContext & UnpluginContext
77 |
78 | getRunner: () => Promise
79 | deps: Map>
80 | attrs: Record
81 | }): Promise<{ code: string; map: any } | undefined> {
82 | const program = babelParse(source, getLang(id))
83 | const s = new MagicStringAST(source)
84 |
85 | const imports = new Map(Object.entries(recordImports()))
86 | const macros = collectMacros()
87 | if (macros.length > 0) {
88 | await executeMacros()
89 | } else {
90 | deps.delete(id)
91 | }
92 |
93 | return generateTransform(s, id)
94 |
95 | async function executeMacros() {
96 | const runner = await getRunner()
97 | deps.set(id, new Set())
98 |
99 | for (const macro of macros) {
100 | const {
101 | node,
102 | id: [local, ...keys],
103 | isAwait,
104 | } = macro
105 | const binding = imports.get(local)!
106 | const [, resolved] = await runner.resolveUrl(binding.source, id)
107 |
108 | let exported
109 | if (
110 | resolved.startsWith('node:') ||
111 | builtinModules.includes(resolved.split('/')[0])
112 | ) {
113 | exported = await import(resolved)
114 | } else {
115 | const module = await runner.executeFile(resolved)
116 | exported = module
117 | }
118 |
119 | const props = [...keys]
120 | if (binding.imported !== '*') props.unshift(binding.imported)
121 | for (const key of props) {
122 | exported = exported?.[key]
123 | }
124 |
125 | if (!exported) {
126 | throw new Error(`Macro ${local} is not existed.`)
127 | }
128 |
129 | let ret: any
130 | if (macro.type === 'call') {
131 | const ctx: MacroContext = {
132 | id,
133 | source,
134 | emitFile: unpluginContext.emitFile,
135 |
136 | unpluginContext,
137 | }
138 | ret = (exported as Function).apply(ctx, macro.args)
139 | } else {
140 | ret = exported
141 | }
142 |
143 | if (isAwait) {
144 | ret = await ret
145 | }
146 |
147 | s.overwriteNode(
148 | node,
149 | ret === undefined ? 'undefined' : JSON.stringify(ret),
150 | )
151 |
152 | deps.get(id)!.add(resolved)
153 | }
154 | }
155 |
156 | function collectMacros() {
157 | const macros: Macro[] = []
158 | let scope = attachScopes(program, 'scope')
159 | const parentStack: Node[] = []
160 |
161 | walkAST>(program, {
162 | enter(node, parent) {
163 | parent && parentStack.push(parent)
164 | if (node.scope) scope = node.scope
165 |
166 | if (
167 | node.type.startsWith('TS') &&
168 | !TS_NODE_TYPES.includes(node.type as any)
169 | ) {
170 | this.skip()
171 | return
172 | }
173 |
174 | const isAwait = parent?.type === 'AwaitExpression'
175 |
176 | if (node.type === 'TaggedTemplateExpression') {
177 | node = {
178 | ...(node as any),
179 | type: 'CallExpression',
180 | callee: node.tag,
181 | arguments: [node.quasi],
182 | }
183 | }
184 |
185 | if (
186 | node.type === 'CallExpression' &&
187 | isTypeOf(node.callee, ['Identifier', 'MemberExpression'])
188 | ) {
189 | let id: string[]
190 | try {
191 | id = resolveIdentifier(node.callee)
192 | } catch {
193 | return
194 | }
195 | if (!imports.has(id[0]) || scope.contains(id[0])) return
196 | const args = node.arguments.map((arg) => {
197 | if (isLiteralType(arg)) return resolveLiteral(arg)
198 | try {
199 | if (isTypeOf(arg, ['ObjectExpression', 'ArrayExpression']))
200 | return new Function(
201 | `return (${source.slice(arg.start!, arg.end!)})`,
202 | )()
203 | } catch {}
204 | throw new Error('Macro arguments cannot be resolved.')
205 | })
206 |
207 | macros.push({
208 | type: 'call',
209 | node: isAwait ? parent : node,
210 | id,
211 | args,
212 | isAwait,
213 | })
214 | this.skip()
215 | } else if (
216 | isTypeOf(node, ['Identifier', 'MemberExpression']) &&
217 | (!parent || isReferenced(node, parent, parentStack.at(-2)))
218 | ) {
219 | let id: string[]
220 | try {
221 | id = resolveIdentifier(node)
222 | } catch {
223 | return
224 | }
225 | if (!imports.has(id[0]) || scope.contains(id[0])) return
226 |
227 | macros.push({
228 | type: 'identifier',
229 | node: isAwait ? parent : node,
230 | id,
231 | isAwait,
232 | })
233 | this.skip()
234 | }
235 | },
236 | leave(node) {
237 | if (node.scope) scope = scope.parent!
238 | parentStack.pop()
239 | },
240 | })
241 |
242 | return macros
243 | }
244 |
245 | function recordImports() {
246 | const imports: Record = {}
247 | for (const node of program.body) {
248 | if (
249 | node.type === 'ImportDeclaration' &&
250 | node.importKind !== 'type' &&
251 | node.attributes &&
252 | checkImportAttributes(attrs, node.attributes)
253 | ) {
254 | s.removeNode(node)
255 | walkImportDeclaration(imports, node)
256 | }
257 | }
258 | return imports
259 | }
260 | }
261 |
262 | function checkImportAttributes(
263 | expected: Record,
264 | actual: ImportAttribute[],
265 | ) {
266 | const actualAttrs = Object.fromEntries(
267 | actual.map((attr) => [resolveObjectKey(attr), attr.value.value]),
268 | )
269 | return Object.entries(expected).every(
270 | ([key, expectedValue]) => actualAttrs[key] === expectedValue,
271 | )
272 | }
273 |
--------------------------------------------------------------------------------
/src/core/options.ts:
--------------------------------------------------------------------------------
1 | import type { FilterPattern } from 'unplugin'
2 | import type { InlineConfig, ViteDevServer } from 'vite'
3 |
4 | /**
5 | * Represents the options for the plugin.
6 | */
7 | export interface Options {
8 | /**
9 | * The patterns of files to include.
10 | * @default [/\.[cm]?[jt]sx?$/]
11 | */
12 | include?: FilterPattern
13 |
14 | /**
15 | * The patterns of files to exclude.
16 | * @default [/node_modules/]
17 | */
18 | exclude?: FilterPattern
19 |
20 | /**
21 | * The Vite dev server instance.
22 | *
23 | * If not provided and the bundler is Vite, it will reuse the current dev server.
24 | * If not provided, it will try to use `viteConfig` to create one.
25 | */
26 | viteServer?: ViteDevServer | false
27 |
28 | /**
29 | * The Vite configuration.
30 | * Available when `viteServer` is not provided.
31 | * @see https://vitejs.dev/config/
32 | */
33 | viteConfig?: InlineConfig
34 |
35 | /**
36 | * Adjusts the plugin order (only works for Vite and Webpack).
37 | * @default 'pre'
38 | */
39 | enforce?: 'pre' | 'post' | undefined
40 |
41 | /**
42 | * The mapping of import attributes.
43 | * @default { "type": "macro" }
44 | */
45 | attrs?: Record
46 | }
47 |
48 | /**
49 | * Represents the resolved options for the plugin.
50 | */
51 | export type OptionsResolved = Omit<
52 | Required,
53 | 'enforce' | 'viteServer'
54 | > & {
55 | enforce?: Options['enforce']
56 | viteServer?: Options['viteServer']
57 | }
58 |
59 | /**
60 | * Resolves the options for the plugin.
61 | *
62 | * @param options - The options to resolve.
63 | * @returns The resolved options.
64 | */
65 | export function resolveOptions(options: Options): OptionsResolved {
66 | return {
67 | include: options.include || [/\.[cm]?[jt]sx?$/],
68 | exclude: options.exclude || [/node_modules/],
69 | viteServer: options.viteServer,
70 | viteConfig: options.viteConfig || {},
71 | enforce: 'enforce' in options ? options.enforce : 'pre',
72 | attrs: options.attrs || { type: 'macro' },
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/esbuild.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for esbuild plugin. Requires esbuild >= 0.15
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Esbuild plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // esbuild.config.js
15 | * import { build } from 'esbuild'
16 | *
17 | * build({
18 | * plugins: [require('unplugin-macros/esbuild')()],
19 | * })
20 | * ```
21 | */
22 | const esbuild = unplugin.esbuild as typeof unplugin.esbuild
23 | export default esbuild
24 | export { esbuild as 'module.exports' }
25 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for main unplugin.
3 | * @module
4 | */
5 |
6 | import { createUnplugin, type UnpluginInstance } from 'unplugin'
7 | import { ViteNodeRunner } from 'vite-node/client'
8 | import { ViteNodeServer } from 'vite-node/server'
9 | import { installSourcemapsSupport } from 'vite-node/source-map'
10 | import { transformMacros } from './core'
11 | import { resolveOptions, type Options } from './core/options'
12 | import type { ModuleNode, ViteDevServer } from 'vite'
13 |
14 | export type { MacroContext, Options } from './core'
15 |
16 | /**
17 | * The main unplugin instance.
18 | */
19 | const plugin: UnpluginInstance = createUnplugin<
20 | Options | undefined,
21 | false
22 | >((rawOptions = {}) => {
23 | const { include, exclude, ...options } = resolveOptions(rawOptions)
24 |
25 | let isBuiltinServer: boolean
26 | let server: ViteDevServer
27 | let node: ViteNodeServer
28 | let runner: ViteNodeRunner | undefined
29 |
30 | const deps: Map> = new Map()
31 |
32 | let initPromise: Promise | undefined
33 | function init() {
34 | if (initPromise) return initPromise
35 | return (initPromise = (async () => {
36 | isBuiltinServer = !options.viteServer
37 | server = options.viteServer || (await initServer())
38 | initRunner()
39 | })())
40 | }
41 |
42 | async function initServer() {
43 | const { createServer } = await import('vite')
44 | const server = await createServer({
45 | ...options.viteConfig,
46 | optimizeDeps: {
47 | include: [],
48 | noDiscovery: true,
49 | },
50 | })
51 | await server.pluginContainer.buildStart({})
52 | return server
53 | }
54 |
55 | function initRunner() {
56 | // create vite-node server
57 | node = new ViteNodeServer(server)
58 |
59 | // fixes stacktraces in Errors
60 | installSourcemapsSupport({
61 | getSourceMap: (source) => node.getSourceMap(source),
62 | })
63 |
64 | // create vite-node runner
65 | runner = new ViteNodeRunner({
66 | root: server.config.root,
67 | base: server.config.base,
68 | // when having the server and runner in a different context,
69 | // you will need to handle the communication between them
70 | // and pass to this function
71 | fetchModule(id) {
72 | return node.fetchModule(id)
73 | },
74 | resolveId(id, importer) {
75 | return node.resolveId(id, importer)
76 | },
77 | })
78 | }
79 |
80 | async function getRunner() {
81 | await init()
82 | return runner!
83 | }
84 |
85 | const name = 'unplugin-macros'
86 | return {
87 | name,
88 | enforce: options.enforce,
89 |
90 | buildEnd() {
91 | if (isBuiltinServer && server) {
92 | // close the built-in vite server
93 | return server.close()
94 | }
95 | },
96 |
97 | transform: {
98 | filter: { id: { include, exclude } },
99 | handler(source, id) {
100 | return transformMacros({
101 | source,
102 | id,
103 | getRunner,
104 | deps,
105 | attrs: options.attrs,
106 | unpluginContext: this,
107 | })
108 | },
109 | },
110 |
111 | vite: {
112 | configureServer(server) {
113 | if (options.viteServer === undefined) {
114 | options.viteServer = server
115 | }
116 | },
117 |
118 | handleHotUpdate({ file, server, modules }) {
119 | if (!runner) return
120 | const cache = runner.moduleCache
121 | const mod = cache.get(file)
122 | if (!mod) return
123 |
124 | node.fetchCache.delete(file)
125 | cache.invalidateModule(mod)
126 |
127 | const affected = new Set()
128 |
129 | for (const [id, macrosIds] of deps.entries()) {
130 | if (!macrosIds.has(file)) continue
131 | server.moduleGraph
132 | .getModulesByFile(id)
133 | ?.forEach((m) => affected.add(m))
134 | }
135 |
136 | return [...affected, ...modules]
137 | },
138 | },
139 | }
140 | })
141 | export default plugin
142 |
--------------------------------------------------------------------------------
/src/rolldown.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for Rolldown plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Rolldown plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // rolldown.config.js
15 | * import Macros from 'unplugin-macros/rolldown'
16 | *
17 | * export default {
18 | * plugins: [Macros()],
19 | * }
20 | * ```
21 | */
22 | const rolldown = unplugin.rolldown as typeof unplugin.rolldown
23 | export default rolldown
24 | export { rolldown as 'module.exports' }
25 |
--------------------------------------------------------------------------------
/src/rollup.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for Rollup plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Rollup plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // rollup.config.js
15 | * import Macros from 'unplugin-macros/rollup'
16 | *
17 | * export default {
18 | * plugins: [Macros()],
19 | * }
20 | * ```
21 | */
22 | const rollup = unplugin.rollup as typeof unplugin.rollup
23 | export default rollup
24 | export { rollup as 'module.exports' }
25 |
--------------------------------------------------------------------------------
/src/rspack.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for rspack plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Rspack plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // rspack.config.js
15 | * module.exports = {
16 | * plugins: [require('unplugin-macros/rspack')()],
17 | * }
18 | * ```
19 | */
20 | const rspack = unplugin.rspack as typeof unplugin.rspack
21 | export default rspack
22 | export { rspack as 'module.exports' }
23 |
--------------------------------------------------------------------------------
/src/vite.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for Vite plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Vite plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // vite.config.ts
15 | * import Macros from 'unplugin-macros/vite'
16 | *
17 | * export default defineConfig({
18 | * plugins: [Macros()],
19 | * })
20 | * ```
21 | */
22 | const vite = unplugin.vite as typeof unplugin.vite
23 | export default vite
24 | export { vite as 'module.exports' }
25 |
--------------------------------------------------------------------------------
/src/webpack.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This entry file is for webpack plugin.
3 | *
4 | * @module
5 | */
6 |
7 | import unplugin from './index'
8 |
9 | /**
10 | * Webpack plugin
11 | *
12 | * @example
13 | * ```ts
14 | * // webpack.config.js
15 | * module.exports = {
16 | * plugins: [require('unplugin-macros/webpack')()],
17 | * }
18 | * ```
19 | */
20 | const webpack = unplugin.webpack as typeof unplugin.webpack
21 | export default webpack
22 | export { webpack as 'module.exports' }
23 |
--------------------------------------------------------------------------------
/tests/__snapshots__/fixtures.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`fixture > tests/fixtures/args.js 1`] = `
4 | "// args.js
5 | 0 === 0;
6 | "1" === '1';
7 | false === false;
8 | null === null;
9 | undefined === undefined;
10 | console.log({"a":"b"});
11 | console.log([1,false,"hello"]);
12 | "
13 | `;
14 |
15 | exports[`fixture > tests/fixtures/basic.js 1`] = `
16 | "// basic.js
17 | 0.5 === 0.5;
18 | 1 === 1;
19 | 2 === 2;
20 | 3 === 3;
21 | 1 === 1;
22 |
23 | const { bar = 1 } = {};
24 | "
25 | `;
26 |
27 | exports[`fixture > tests/fixtures/css.js 1`] = `
28 | "// css.js
29 | // prettier-ignore
30 | const style = {"color":"red"};
31 | "
32 | `;
33 |
34 | exports[`fixture > tests/fixtures/ctx.js 1`] = `
35 | "// ctx.js
36 | "ctx.js" === 'ctx.js';
37 | "
38 | `;
39 |
40 | exports[`fixture > tests/fixtures/dedent.js 1`] = `
41 | "// dedent.js
42 | const msg = "if (true) {\\n const a = 'b'\\n}";
43 | "
44 | `;
45 |
46 | exports[`fixture > tests/fixtures/error-assert.js 1`] = `[SyntaxError: The \`assert\` keyword in import attributes is deprecated and it has been replaced by the \`with\` keyword. You can enable the \`deprecatedImportAssert\` parser plugin to suppress this error. (1:42)]`;
47 |
48 | exports[`fixture > tests/fixtures/import-namespace.js 1`] = `
49 | "// import-namespace.js
50 | 0.5 === 0.5;
51 | "
52 | `;
53 |
54 | exports[`fixture > tests/fixtures/nested-object.js 1`] = `
55 | "// nested-object.js
56 | "foo" === 'foo';
57 | "
58 | `;
59 |
60 | exports[`fixture > tests/fixtures/node-builtin.js 1`] = `
61 | "// node-builtin.js
62 | "LE" === 'LE'
63 | ;("* text=auto eol=lf\\n") ===
64 | // prettier-ignore
65 | "* text=auto eol=lf\\n";
66 | "
67 | `;
68 |
69 | exports[`fixture > tests/fixtures/non-macros.js 1`] = `
70 | "// non-macros.js
71 | {
72 | const getRandom = () => 2;
73 | getRandom() === 2;
74 | }
75 |
76 | ''.toString();
77 |
78 | const genHex = () =>
79 | Math.floor(Math.random() * 255)
80 | .toString(16)
81 | .padStart(2, '0');
82 |
83 | const foo = {
84 | toString: '',
85 | };
86 |
87 | class TestCls {
88 | toString() {}
89 | #toString() {}
90 | }
91 | "
92 | `;
93 |
94 | exports[`fixture > tests/fixtures/promise.js 1`] = `
95 | "// promise.js
96 | const a = "ok";
97 | const b = "ok";
98 | "
99 | `;
100 |
101 | exports[`fixture > tests/fixtures/typescript.ts 1`] = `
102 | "// typescript.js
103 | 10;
104 | "
105 | `;
106 |
--------------------------------------------------------------------------------
/tests/define.test.ts:
--------------------------------------------------------------------------------
1 | import { expectTypeOf, test } from 'vitest'
2 | import { defineMacro } from '../src/api'
3 | import type { MacroContext } from '../src'
4 |
5 | test('define', () => {
6 | const fn = defineMacro(function () {
7 | expectTypeOf(this).toEqualTypeOf()
8 | return 'foo'
9 | })
10 | expectTypeOf(fn).toEqualTypeOf<() => string>()
11 |
12 | const fn2 = defineMacro(function (p: number) {
13 | expectTypeOf(this).toEqualTypeOf()
14 | return p
15 | })
16 | expectTypeOf(fn2).toEqualTypeOf<(p: number) => number>()
17 | })
18 |
--------------------------------------------------------------------------------
/tests/fixtures.test.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import { rollupBuild, testFixtures } from '@sxzz/test-utils'
3 | import { describe, vi } from 'vitest'
4 | import Macros from '../src/rollup'
5 |
6 | vi.spyOn(Math, 'random').mockReturnValue(0.5)
7 |
8 | describe('fixture', async () => {
9 | await testFixtures(
10 | 'tests/fixtures/*.{js,ts}',
11 | async (args, id) =>
12 | (
13 | await rollupBuild(id, [
14 | Macros(),
15 | {
16 | name: 'strip-types',
17 | transform(code) {
18 | return code.replaceAll('as any', '')
19 | },
20 | },
21 | ])
22 | ).snapshot,
23 | {
24 | cwd: resolve(__dirname, '..'),
25 | promise: true,
26 | },
27 | )
28 | })
29 |
--------------------------------------------------------------------------------
/tests/fixtures/args.js:
--------------------------------------------------------------------------------
1 | import { arg } from './macros/args' with { type: 'macro' }
2 |
3 | arg(0) === 0
4 | arg('1') === '1'
5 | arg(false) === false
6 | arg(null) === null
7 | arg() === undefined
8 | console.log(arg({ a: 'b' }))
9 | console.log(arg([1, false, 'hello']))
10 |
--------------------------------------------------------------------------------
/tests/fixtures/basic.js:
--------------------------------------------------------------------------------
1 | import { getRandom } from './macros/rand' with { type: 'macro' }
2 | import { inc } from './macros/inc' with { type: 'macro' }
3 | import { foo } from './macros/var' with { type: 'macro' }
4 |
5 | getRandom() === 0.5
6 | inc() === 1
7 | inc() === 2
8 | inc() === 3
9 | foo === 1
10 |
11 | const { bar = foo } = {}
12 |
--------------------------------------------------------------------------------
/tests/fixtures/css.js:
--------------------------------------------------------------------------------
1 | import { css } from './macros/css' with { type: 'macro' }
2 |
3 | // prettier-ignore
4 | const style = css`color: red`
5 |
--------------------------------------------------------------------------------
/tests/fixtures/ctx.js:
--------------------------------------------------------------------------------
1 | import { getCtx } from './macros/ctx' with { type: 'macro' }
2 |
3 | getCtx() === 'ctx.js'
4 |
--------------------------------------------------------------------------------
/tests/fixtures/dedent.js:
--------------------------------------------------------------------------------
1 | import dedent from 'dedent' with { type: 'macro' }
2 |
3 | const msg = dedent(`
4 | if (true) {
5 | const a = 'b'
6 | }
7 | `)
8 |
--------------------------------------------------------------------------------
/tests/fixtures/error-assert.js:
--------------------------------------------------------------------------------
1 | import { getRandom } from './macros/rand' assert { type: 'macro' }
2 |
3 | console.log(getRandom())
4 |
--------------------------------------------------------------------------------
/tests/fixtures/import-namespace.js:
--------------------------------------------------------------------------------
1 | import * as rand from './macros/rand' with { type: 'macro' }
2 |
3 | rand.getRandom() === 0.5
4 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/args.ts:
--------------------------------------------------------------------------------
1 | export function arg(arg: T) {
2 | return arg
3 | }
4 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/css.ts:
--------------------------------------------------------------------------------
1 | export function css(css: string) {
2 | return { color: 'red' }
3 | }
4 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/ctx.ts:
--------------------------------------------------------------------------------
1 | import { MacroContext } from '../../../src'
2 | import path from 'path'
3 |
4 | export function getCtx(this: MacroContext) {
5 | return path.basename(this.id)
6 | }
7 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/inc.js:
--------------------------------------------------------------------------------
1 | export let i = 0
2 | export function inc() {
3 | return ++i
4 | }
5 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/nested-object.ts:
--------------------------------------------------------------------------------
1 | export const foo = {
2 | a: {
3 | b: {
4 | c: {
5 | d: () => 'foo',
6 | },
7 | },
8 | },
9 | }
10 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/promise.ts:
--------------------------------------------------------------------------------
1 | export function promise() {
2 | return Promise.resolve('ok')
3 | }
4 | export const p = Promise.resolve('ok')
5 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/rand.js:
--------------------------------------------------------------------------------
1 | export function getRandom() {
2 | return Math.random()
3 | }
4 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/string.js:
--------------------------------------------------------------------------------
1 | export function toString(v) {
2 | return v.toString()
3 | }
4 |
--------------------------------------------------------------------------------
/tests/fixtures/macros/var.ts:
--------------------------------------------------------------------------------
1 | export const foo = 1
2 |
--------------------------------------------------------------------------------
/tests/fixtures/nested-object.js:
--------------------------------------------------------------------------------
1 | import * as obj from './macros/nested-object' with { type: 'macro' }
2 |
3 | obj.foo.a.b.c.d() === 'foo'
4 |
--------------------------------------------------------------------------------
/tests/fixtures/node-builtin.js:
--------------------------------------------------------------------------------
1 | import { endianness } from 'node:os' with { type: 'macro' }
2 | import { readFile } from 'fs/promises' with { type: 'macro' }
3 |
4 | endianness() === 'LE'
5 | ;(await readFile('.gitattributes', { encoding: 'utf8' })) ===
6 | // prettier-ignore
7 | "* text=auto eol=lf\n"
8 |
--------------------------------------------------------------------------------
/tests/fixtures/non-macros.js:
--------------------------------------------------------------------------------
1 | import { getRandom } from './macros/rand' with { type: 'macro' }
2 | import { toString } from './macros/string' with { type: 'macro' }
3 |
4 | {
5 | const getRandom = () => 2
6 | getRandom() === 2
7 | }
8 |
9 | ''.toString()
10 |
11 | const genHex = () =>
12 | Math.floor(Math.random() * 255)
13 | .toString(16)
14 | .padStart(2, '0')
15 |
16 | const foo = {
17 | toString: '',
18 | }
19 |
20 | class TestCls {
21 | toString() {}
22 | #toString() {}
23 | }
24 |
--------------------------------------------------------------------------------
/tests/fixtures/promise.js:
--------------------------------------------------------------------------------
1 | import { promise, p } from './macros/promise' with { type: 'macro' }
2 |
3 | const a = await promise()
4 | const b = await p
5 |
--------------------------------------------------------------------------------
/tests/fixtures/typescript.ts:
--------------------------------------------------------------------------------
1 | import { arg } from './macros/args' with { type: 'macro' }
2 |
3 | arg(10) as any
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "lib": ["es2022"],
5 | "moduleDetection": "force",
6 | "module": "preserve",
7 | "moduleResolution": "bundler",
8 | "resolveJsonModule": true,
9 | "types": ["node"],
10 | "strict": true,
11 | "noUnusedLocals": true,
12 | "declaration": true,
13 | "isolatedDeclarations": true,
14 | "esModuleInterop": true,
15 | "isolatedModules": true,
16 | "skipLibCheck": true
17 | },
18 | "include": ["src", "tests"],
19 | "exclude": ["tests/fixtures"]
20 | }
21 |
--------------------------------------------------------------------------------
/tsdown.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'tsdown'
2 |
3 | export default defineConfig({
4 | entry: ['./src/*.ts'],
5 | })
6 |
--------------------------------------------------------------------------------