├── .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 [![npm](https://img.shields.io/npm/v/unplugin-macros.svg)](https://npmjs.com/package/unplugin-macros) [![jsr](https://img.shields.io/endpoint?url=https%3A%2F%2Fjsr-api.sxzz.moe%2Fbadge%2F%40unplugin%2Fmacros)](https://jsr.io/@unplugin/macros) 2 | 3 | [![Unit Test](https://github.com/unplugin/unplugin-macros/actions/workflows/unit-test.yml/badge.svg)](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 | --------------------------------------------------------------------------------