jiek 2.3.2 → 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@ var Koa__default = /*#__PURE__*/_interopDefault(Koa);
37
37
 
38
38
  var name = "jiek";
39
39
  var type = "module";
40
- var version = "2.3.1";
40
+ var version = "2.3.2";
41
41
  var description$1 = "A lightweight toolkit for compiling and managing libraries based on `package.json` metadata and suitable for `Monorepo`.";
42
42
  var author = "YiJie <yijie4188@gmail.com>";
43
43
  var homepage = "https://github.com/NWYLZW/jiek/tree/master/packages/jiek#readme";
@@ -89,6 +89,7 @@ var scripts = {
89
89
  test: "vitest run"
90
90
  };
91
91
  var peerDependencies = {
92
+ "@ast-grep/napi": "^0.32.3",
92
93
  "@pnpm/filter-workspace-packages": "^7.2.13||^8.0.0||^9.0.0||^10.0.0||>=1000.0.0",
93
94
  "@rollup/plugin-terser": "^0.4.4",
94
95
  "esbuild-register": "^3.5.0",
@@ -118,6 +119,7 @@ var dependencies = {
118
119
  rollup: "^4.0.0"
119
120
  };
120
121
  var devDependencies = {
122
+ "@ast-grep/napi": "^0.32.3",
121
123
  "@npm/types": "^1.0.2",
122
124
  "@pnpm/filter-workspace-packages": "^7.2.13",
123
125
  "@pnpm/workspace.pkgs-graph": "^2.0.15",
@@ -3,6 +3,8 @@ import '@rollup/plugin-terser';
3
3
  import 'rollup-plugin-swc3';
4
4
  import 'rollup-plugin-esbuild';
5
5
  import 'rollup';
6
+ import '@ast-grep/napi';
7
+ import 'jiek/rollup-plugin-utils';
6
8
 
7
9
  declare module 'jiek' {
8
10
  interface Config {
@@ -3,6 +3,8 @@ import '@rollup/plugin-terser';
3
3
  import 'rollup-plugin-swc3';
4
4
  import 'rollup-plugin-esbuild';
5
5
  import 'rollup';
6
+ import '@ast-grep/napi';
7
+ import 'jiek/rollup-plugin-utils';
6
8
 
7
9
  declare module 'jiek' {
8
10
  interface Config {
@@ -30,7 +30,7 @@ import 'node:stream/promises';
30
30
 
31
31
  var name = "jiek";
32
32
  var type = "module";
33
- var version = "2.3.1";
33
+ var version = "2.3.2";
34
34
  var description$1 = "A lightweight toolkit for compiling and managing libraries based on `package.json` metadata and suitable for `Monorepo`.";
35
35
  var author = "YiJie <yijie4188@gmail.com>";
36
36
  var homepage = "https://github.com/NWYLZW/jiek/tree/master/packages/jiek#readme";
@@ -82,6 +82,7 @@ var scripts = {
82
82
  test: "vitest run"
83
83
  };
84
84
  var peerDependencies = {
85
+ "@ast-grep/napi": "^0.32.3",
85
86
  "@pnpm/filter-workspace-packages": "^7.2.13||^8.0.0||^9.0.0||^10.0.0||>=1000.0.0",
86
87
  "@rollup/plugin-terser": "^0.4.4",
87
88
  "esbuild-register": "^3.5.0",
@@ -111,6 +112,7 @@ var dependencies = {
111
112
  rollup: "^4.0.0"
112
113
  };
113
114
  var devDependencies = {
115
+ "@ast-grep/napi": "^0.32.3",
114
116
  "@npm/types": "^1.0.2",
115
117
  "@pnpm/filter-workspace-packages": "^7.2.13",
116
118
  "@pnpm/workspace.pkgs-graph": "^2.0.15",
@@ -2,16 +2,38 @@ import * as _rollup_plugin_terser from '@rollup/plugin-terser';
2
2
  import * as rollup_plugin_swc3 from 'rollup-plugin-swc3';
3
3
  import * as rollup_plugin_esbuild from 'rollup-plugin-esbuild';
4
4
  import { OutputOptions, InputPluginOption } from 'rollup';
5
+ import { SgNode, Lang } from '@ast-grep/napi';
6
+ import { FilterOptions } from 'jiek/rollup-plugin-utils';
5
7
 
6
- interface ReplacementFuncCtx {
8
+ type Mode = 'string' | 'ast-grep';
9
+ type ReplacementFuncCtx = {
7
10
  type: 'transform' | 'renderChunk';
8
11
  id: string;
9
12
  code: string;
13
+ mode: Mode;
14
+ } & ({
15
+ mode?: 'string';
10
16
  start: number;
11
17
  end: number;
12
- }
13
- type ReplacementFunc = (ctx: ReplacementFuncCtx) => string;
14
- type Replacements = Record<string, string | ReplacementFunc>;
18
+ } | {
19
+ mode?: 'ast-grep';
20
+ $: ((name: string) => string | undefined) & ((template: {
21
+ raw: readonly string[];
22
+ }) => string | undefined);
23
+ node: SgNode;
24
+ lang: Lang;
25
+ });
26
+ type Falsy = false | null | undefined;
27
+ type ReplacementFunc = (ctx: ReplacementFuncCtx) => string | Falsy;
28
+ type Replacements = Record<string, string | Falsy | ReplacementFunc>;
29
+ type ReplaceOptions = FilterOptions & {
30
+ /**
31
+ * @default 'string'
32
+ */
33
+ mode?: Mode;
34
+ sourcemap?: boolean;
35
+ values?: Replacements;
36
+ };
15
37
 
16
38
  type Mapping2ROO<K extends keyof OutputOptions> = OutputOptions[K] | {
17
39
  js?: OutputOptions[K];
@@ -137,6 +159,7 @@ interface TemplateOptions {
137
159
  * ```
138
160
  */
139
161
  replacements?: Replacements;
162
+ replacementsOptions?: Pick<ReplaceOptions, 'mode' | 'include' | 'exclude'>;
140
163
  }
141
164
 
142
165
  export { BUILDER_TYPES, BUILDER_TYPE_PACKAGE_NAME_MAP, type ConfigGenerateContext, type Mapping2ROO, type OutputControl, type TemplateOptions };
@@ -2,16 +2,38 @@ import * as _rollup_plugin_terser from '@rollup/plugin-terser';
2
2
  import * as rollup_plugin_swc3 from 'rollup-plugin-swc3';
3
3
  import * as rollup_plugin_esbuild from 'rollup-plugin-esbuild';
4
4
  import { OutputOptions, InputPluginOption } from 'rollup';
5
+ import { SgNode, Lang } from '@ast-grep/napi';
6
+ import { FilterOptions } from 'jiek/rollup-plugin-utils';
5
7
 
6
- interface ReplacementFuncCtx {
8
+ type Mode = 'string' | 'ast-grep';
9
+ type ReplacementFuncCtx = {
7
10
  type: 'transform' | 'renderChunk';
8
11
  id: string;
9
12
  code: string;
13
+ mode: Mode;
14
+ } & ({
15
+ mode?: 'string';
10
16
  start: number;
11
17
  end: number;
12
- }
13
- type ReplacementFunc = (ctx: ReplacementFuncCtx) => string;
14
- type Replacements = Record<string, string | ReplacementFunc>;
18
+ } | {
19
+ mode?: 'ast-grep';
20
+ $: ((name: string) => string | undefined) & ((template: {
21
+ raw: readonly string[];
22
+ }) => string | undefined);
23
+ node: SgNode;
24
+ lang: Lang;
25
+ });
26
+ type Falsy = false | null | undefined;
27
+ type ReplacementFunc = (ctx: ReplacementFuncCtx) => string | Falsy;
28
+ type Replacements = Record<string, string | Falsy | ReplacementFunc>;
29
+ type ReplaceOptions = FilterOptions & {
30
+ /**
31
+ * @default 'string'
32
+ */
33
+ mode?: Mode;
34
+ sourcemap?: boolean;
35
+ values?: Replacements;
36
+ };
15
37
 
16
38
  type Mapping2ROO<K extends keyof OutputOptions> = OutputOptions[K] | {
17
39
  js?: OutputOptions[K];
@@ -137,6 +159,7 @@ interface TemplateOptions {
137
159
  * ```
138
160
  */
139
161
  replacements?: Replacements;
162
+ replacementsOptions?: Pick<ReplaceOptions, 'mode' | 'include' | 'exclude'>;
140
163
  }
141
164
 
142
165
  export { BUILDER_TYPES, BUILDER_TYPE_PACKAGE_NAME_MAP, type ConfigGenerateContext, type Mapping2ROO, type OutputControl, type TemplateOptions };
@@ -65,6 +65,8 @@ var progress = (options = {}) => {
65
65
  };
66
66
  };
67
67
 
68
+ var require$1 = require;
69
+
68
70
  var replace = rollupPluginUtils.definePlugin((options = {}) => {
69
71
  const {
70
72
  include = [/\.[cm]?[tj]sx?$/],
@@ -72,39 +74,98 @@ var replace = rollupPluginUtils.definePlugin((options = {}) => {
72
74
  values = {},
73
75
  sourcemap
74
76
  } = options;
77
+ let { mode = "string" } = options;
75
78
  const allValues = { ...values };
76
79
  const allKeys = Object.keys(allValues);
77
80
  const filter = rollupPluginUtils.createFilter({ include, exclude });
78
- const replaceAll = (ctx, code) => {
81
+ const replaceAll = async (ctx, code) => {
79
82
  const ms = new MagicString__default.default(code);
80
- allKeys.forEach((key) => {
81
- const reg = new RegExp(key, "g");
82
- let match;
83
- while (match = reg.exec(code)) {
84
- const start = match.index;
85
- const end = start + key.length;
86
- ms.overwrite(
87
- match.index,
88
- match.index + key.length,
89
- typeof allValues[key] === "function" ? allValues[key]({
83
+ if (mode === "string") {
84
+ allKeys.forEach((key) => {
85
+ const reg = new RegExp(key, "g");
86
+ let match;
87
+ while (match = reg.exec(code)) {
88
+ const start = match.index;
89
+ const end = start + key.length;
90
+ const value = typeof allValues[key] === "function" ? allValues[key]({
90
91
  ...ctx,
91
92
  code,
92
93
  start,
93
- end
94
- }) : allValues[key]
95
- );
94
+ end,
95
+ mode: "string"
96
+ }) : allValues[key];
97
+ if ([null, void 0, false].includes(value)) continue;
98
+ ms.overwrite(
99
+ match.index,
100
+ match.index + key.length,
101
+ value
102
+ );
103
+ }
104
+ });
105
+ } else if (mode === "ast-grep") {
106
+ const ext = node_path.extname(ctx.id);
107
+ const { parse, Lang } = await import('@ast-grep/napi');
108
+ let lang;
109
+ if (/[cm]?tsx?/.test(ext)) {
110
+ lang = Lang.TypeScript;
96
111
  }
97
- });
112
+ if (/[cm]?jsx?/.test(ext)) {
113
+ lang = Lang.JavaScript;
114
+ }
115
+ if (/json?/.test(ext)) {
116
+ lang = Lang.Json;
117
+ }
118
+ if (lang == null) return;
119
+ const root = parse(lang, code).root();
120
+ allKeys.forEach((key) => {
121
+ root.findAll(key).forEach((node) => {
122
+ const { start, end } = node.range();
123
+ const newValue = typeof allValues[key] === "function" ? allValues[key]({
124
+ ...ctx,
125
+ code,
126
+ mode: "ast-grep",
127
+ node,
128
+ lang,
129
+ $: (input) => {
130
+ if (typeof input === "string") {
131
+ return node.getMatch(input)?.text();
132
+ }
133
+ if ("raw" in input) {
134
+ return node.getMatch(input.raw[0])?.text();
135
+ }
136
+ }
137
+ }) : allValues[key];
138
+ if ([null, void 0, false].includes(newValue)) return;
139
+ ms.overwrite(
140
+ start.index,
141
+ end.index,
142
+ newValue
143
+ );
144
+ });
145
+ });
146
+ }
98
147
  return ms;
99
148
  };
100
149
  return {
101
150
  name: "jiek:replace",
151
+ buildStart() {
152
+ if (mode === "ast-grep") {
153
+ try {
154
+ require$1.resolve("@ast-grep/napi");
155
+ this.warn(
156
+ "You are using `ast-grep` mode, please make sure you have installed `@ast-grep/napi`"
157
+ );
158
+ } catch {
159
+ mode = "string";
160
+ }
161
+ }
162
+ },
102
163
  transform: {
103
164
  order: "pre",
104
- handler(code, id) {
165
+ async handler(code, id) {
105
166
  if (allKeys.length === 0) return;
106
167
  if (filter(id)) return;
107
- const ms = replaceAll({ type: "transform", id }, code);
168
+ const ms = await replaceAll({ type: "transform", id }, code);
108
169
  if (ms == null) return;
109
170
  return {
110
171
  code: ms.toString(),
@@ -114,10 +175,10 @@ var replace = rollupPluginUtils.definePlugin((options = {}) => {
114
175
  },
115
176
  renderChunk: {
116
177
  order: "post",
117
- handler(code, { fileName: id }) {
178
+ async handler(code, { fileName: id }) {
118
179
  if (allKeys.length === 0) return;
119
180
  if (filter(id)) return;
120
- const ms = replaceAll({ type: "renderChunk", id }, code);
181
+ const ms = await replaceAll({ type: "renderChunk", id }, code);
121
182
  if (ms == null) return;
122
183
  return {
123
184
  code: ms.toString(),
@@ -428,6 +489,7 @@ const generateConfigs = (context, {
428
489
  const rollupOptions = [];
429
490
  const commonPlugins = ({ sourcemap }) => [
430
491
  replace({
492
+ ...build.replacementsOptions,
431
493
  sourcemap: sourcemap === "hidden" ? false : !!sourcemap,
432
494
  values: build.replacements
433
495
  }),
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs';
2
- import { relative, resolve, dirname, extname } from 'node:path';
2
+ import { extname, relative, resolve, dirname } from 'node:path';
3
3
  import process from 'node:process';
4
4
  import { getAllLeafs } from '@jiek/pkger/entrypoints';
5
5
  import { dts } from '@jiek/rollup-plugin-dts';
@@ -17,9 +17,9 @@ import { loadConfig } from '#~/utils/loadConfig';
17
17
  import { recursiveListFiles } from '#~/utils/recursiveListFiles';
18
18
  import { getOutDirs, resolveExports } from '#~/utils/resolveExports';
19
19
  import { getCompilerOptionsByFilePath } from '#~/utils/ts';
20
+ import { createRequire as createRequire$1, builtinModules } from 'node:module';
20
21
  import { definePlugin, createFilter } from 'jiek/rollup-plugin-utils';
21
22
  import MagicString from 'magic-string';
22
- import { builtinModules } from 'node:module';
23
23
 
24
24
  const CREATE_REQUIRE_VIRTUAL_MODULE_NAME = "jiek:create-require";
25
25
  const INSERT_STR = (isESM) => `
@@ -53,6 +53,8 @@ var progress = (options = {}) => {
53
53
  };
54
54
  };
55
55
 
56
+ var require = /* @__PURE__ */ createRequire$1(import.meta.url);
57
+
56
58
  var replace = definePlugin((options = {}) => {
57
59
  const {
58
60
  include = [/\.[cm]?[tj]sx?$/],
@@ -60,39 +62,98 @@ var replace = definePlugin((options = {}) => {
60
62
  values = {},
61
63
  sourcemap
62
64
  } = options;
65
+ let { mode = "string" } = options;
63
66
  const allValues = { ...values };
64
67
  const allKeys = Object.keys(allValues);
65
68
  const filter = createFilter({ include, exclude });
66
- const replaceAll = (ctx, code) => {
69
+ const replaceAll = async (ctx, code) => {
67
70
  const ms = new MagicString(code);
68
- allKeys.forEach((key) => {
69
- const reg = new RegExp(key, "g");
70
- let match;
71
- while (match = reg.exec(code)) {
72
- const start = match.index;
73
- const end = start + key.length;
74
- ms.overwrite(
75
- match.index,
76
- match.index + key.length,
77
- typeof allValues[key] === "function" ? allValues[key]({
71
+ if (mode === "string") {
72
+ allKeys.forEach((key) => {
73
+ const reg = new RegExp(key, "g");
74
+ let match;
75
+ while (match = reg.exec(code)) {
76
+ const start = match.index;
77
+ const end = start + key.length;
78
+ const value = typeof allValues[key] === "function" ? allValues[key]({
78
79
  ...ctx,
79
80
  code,
80
81
  start,
81
- end
82
- }) : allValues[key]
83
- );
82
+ end,
83
+ mode: "string"
84
+ }) : allValues[key];
85
+ if ([null, void 0, false].includes(value)) continue;
86
+ ms.overwrite(
87
+ match.index,
88
+ match.index + key.length,
89
+ value
90
+ );
91
+ }
92
+ });
93
+ } else if (mode === "ast-grep") {
94
+ const ext = extname(ctx.id);
95
+ const { parse, Lang } = await import('@ast-grep/napi');
96
+ let lang;
97
+ if (/[cm]?tsx?/.test(ext)) {
98
+ lang = Lang.TypeScript;
84
99
  }
85
- });
100
+ if (/[cm]?jsx?/.test(ext)) {
101
+ lang = Lang.JavaScript;
102
+ }
103
+ if (/json?/.test(ext)) {
104
+ lang = Lang.Json;
105
+ }
106
+ if (lang == null) return;
107
+ const root = parse(lang, code).root();
108
+ allKeys.forEach((key) => {
109
+ root.findAll(key).forEach((node) => {
110
+ const { start, end } = node.range();
111
+ const newValue = typeof allValues[key] === "function" ? allValues[key]({
112
+ ...ctx,
113
+ code,
114
+ mode: "ast-grep",
115
+ node,
116
+ lang,
117
+ $: (input) => {
118
+ if (typeof input === "string") {
119
+ return node.getMatch(input)?.text();
120
+ }
121
+ if ("raw" in input) {
122
+ return node.getMatch(input.raw[0])?.text();
123
+ }
124
+ }
125
+ }) : allValues[key];
126
+ if ([null, void 0, false].includes(newValue)) return;
127
+ ms.overwrite(
128
+ start.index,
129
+ end.index,
130
+ newValue
131
+ );
132
+ });
133
+ });
134
+ }
86
135
  return ms;
87
136
  };
88
137
  return {
89
138
  name: "jiek:replace",
139
+ buildStart() {
140
+ if (mode === "ast-grep") {
141
+ try {
142
+ require.resolve("@ast-grep/napi");
143
+ this.warn(
144
+ "You are using `ast-grep` mode, please make sure you have installed `@ast-grep/napi`"
145
+ );
146
+ } catch {
147
+ mode = "string";
148
+ }
149
+ }
150
+ },
90
151
  transform: {
91
152
  order: "pre",
92
- handler(code, id) {
153
+ async handler(code, id) {
93
154
  if (allKeys.length === 0) return;
94
155
  if (filter(id)) return;
95
- const ms = replaceAll({ type: "transform", id }, code);
156
+ const ms = await replaceAll({ type: "transform", id }, code);
96
157
  if (ms == null) return;
97
158
  return {
98
159
  code: ms.toString(),
@@ -102,10 +163,10 @@ var replace = definePlugin((options = {}) => {
102
163
  },
103
164
  renderChunk: {
104
165
  order: "post",
105
- handler(code, { fileName: id }) {
166
+ async handler(code, { fileName: id }) {
106
167
  if (allKeys.length === 0) return;
107
168
  if (filter(id)) return;
108
- const ms = replaceAll({ type: "renderChunk", id }, code);
169
+ const ms = await replaceAll({ type: "renderChunk", id }, code);
109
170
  if (ms == null) return;
110
171
  return {
111
172
  code: ms.toString(),
@@ -416,6 +477,7 @@ const generateConfigs = (context, {
416
477
  const rollupOptions = [];
417
478
  const commonPlugins = ({ sourcemap }) => [
418
479
  replace({
480
+ ...build.replacementsOptions,
419
481
  sourcemap: sourcemap === "hidden" ? false : !!sourcemap,
420
482
  values: build.replacements
421
483
  }),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jiek",
3
3
  "type": "module",
4
- "version": "2.3.2",
4
+ "version": "2.3.3",
5
5
  "description": "A lightweight toolkit for compiling and managing libraries based on `package.json` metadata and suitable for `Monorepo`.",
6
6
  "author": "YiJie <yijie4188@gmail.com>",
7
7
  "homepage": "https://github.com/NWYLZW/jiek/tree/master/packages/jiek#readme",
@@ -56,6 +56,7 @@
56
56
  "jb": "bin/build.cjs"
57
57
  },
58
58
  "peerDependencies": {
59
+ "@ast-grep/napi": "^0.32.3",
59
60
  "@pnpm/filter-workspace-packages": "^7.2.13||^8.0.0||^9.0.0||^10.0.0||>=1000.0.0",
60
61
  "@rollup/plugin-terser": "^0.4.4",
61
62
  "esbuild-register": "^3.5.0",
@@ -85,6 +86,9 @@
85
86
  "@jiek/utils": "^0.2.3"
86
87
  },
87
88
  "peerDependenciesMeta": {
89
+ "@ast-grep/napi": {
90
+ "optional": true
91
+ },
88
92
  "@pnpm/filter-workspace-packages": {
89
93
  "optional": true
90
94
  },
@@ -1,5 +1,5 @@
1
1
  import type { InputPluginOption, OutputOptions } from 'rollup'
2
- import { Replacements } from './plugins/replace'
2
+ import { ReplaceOptions, Replacements } from './plugins/replace'
3
3
 
4
4
  export type Mapping2ROO<K extends keyof OutputOptions> = OutputOptions[K] | {
5
5
  js?: OutputOptions[K]
@@ -148,4 +148,5 @@ export interface TemplateOptions {
148
148
  * ```
149
149
  */
150
150
  replacements?: Replacements
151
+ replacementsOptions?: Pick<ReplaceOptions, 'mode' | 'include' | 'exclude'>
151
152
  }
@@ -428,6 +428,7 @@ const generateConfigs = (
428
428
  { sourcemap }: { sourcemap?: string | boolean }
429
429
  ): InputPluginOption[] => [
430
430
  replace({
431
+ ...build.replacementsOptions,
431
432
  sourcemap: sourcemap === 'hidden' ? false : !!sourcemap,
432
433
  values: build.replacements
433
434
  }),
@@ -1,74 +1,163 @@
1
+ import { extname } from 'node:path'
2
+
3
+ import type { Lang, SgNode } from '@ast-grep/napi'
1
4
  import type { FilterOptions } from 'jiek/rollup-plugin-utils'
2
5
  import { createFilter, definePlugin } from 'jiek/rollup-plugin-utils'
3
6
  import MagicString from 'magic-string'
4
7
 
5
- export interface ReplacementFuncCtx {
6
- type: 'transform' | 'renderChunk'
7
- id: string
8
- code: string
9
- start: number
10
- end: number
11
- }
8
+ export type Mode = 'string' | 'ast-grep'
9
+
10
+ export type ReplacementFuncCtx =
11
+ & {
12
+ type: 'transform' | 'renderChunk'
13
+ id: string
14
+ code: string
15
+ mode: Mode
16
+ }
17
+ & (
18
+ | {
19
+ mode?: 'string'
20
+ start: number
21
+ end: number
22
+ }
23
+ | {
24
+ mode?: 'ast-grep'
25
+ $:
26
+ & ((name: string) => string | undefined)
27
+ & ((template: { raw: readonly string[] }) => string | undefined)
28
+ node: SgNode
29
+ lang: Lang
30
+ }
31
+ )
32
+
33
+ type Falsy = false | null | undefined
12
34
 
13
- export type ReplacementFunc = (ctx: ReplacementFuncCtx) => string
35
+ export type ReplacementFunc = (ctx: ReplacementFuncCtx) => string | Falsy
14
36
 
15
37
  export type Replacements = Record<
16
38
  string,
17
- string | ReplacementFunc
39
+ string | Falsy | ReplacementFunc
18
40
  >
19
41
 
20
- export type Options =
42
+ export type ReplaceOptions =
21
43
  & FilterOptions
22
44
  & {
45
+ /**
46
+ * @default 'string'
47
+ */
48
+ mode?: Mode
23
49
  sourcemap?: boolean
24
50
  values?: Replacements
25
51
  }
26
52
 
27
- export default definePlugin((options: Options = {}) => {
53
+ export default definePlugin((options: ReplaceOptions = {}) => {
28
54
  const {
29
55
  include = [/\.[cm]?[tj]sx?$/],
30
56
  exclude = [/node_modules/],
31
57
  values = {},
32
58
  sourcemap
33
59
  } = options
60
+ let { mode = 'string' } = options
34
61
  const allValues = { ...values }
35
62
  const allKeys = Object.keys(allValues)
36
63
  const filter = createFilter({ include, exclude })
37
64
 
38
- const replaceAll = (ctx: Pick<ReplacementFuncCtx, 'type' | 'id'>, code: string) => {
65
+ const replaceAll = async (ctx: Pick<ReplacementFuncCtx, 'type' | 'id'>, code: string) => {
39
66
  const ms = new MagicString(code)
40
- allKeys.forEach(key => {
41
- const reg = new RegExp(key, 'g')
42
- let match
43
- // eslint-disable-next-line no-cond-assign
44
- while ((match = reg.exec(code))) {
45
- const start = match.index
46
- const end = start + key.length
47
- ms.overwrite(
48
- match.index,
49
- match.index + key.length,
50
- typeof allValues[key] === 'function'
67
+ if (mode === 'string') {
68
+ allKeys.forEach(key => {
69
+ const reg = new RegExp(key, 'g')
70
+ let match
71
+ // eslint-disable-next-line no-cond-assign
72
+ while ((match = reg.exec(code))) {
73
+ const start = match.index
74
+ const end = start + key.length
75
+ const value = typeof allValues[key] === 'function'
51
76
  ? allValues[key]({
52
77
  ...ctx,
53
78
  code,
54
79
  start,
55
- end
80
+ end,
81
+ mode: 'string'
56
82
  })
57
83
  : allValues[key]
58
- )
84
+ if (([null, undefined, false] as unknown[]).includes(value)) continue
85
+ ms.overwrite(
86
+ match.index,
87
+ match.index + key.length,
88
+ value as string
89
+ )
90
+ }
91
+ })
92
+ } else if (mode === 'ast-grep') {
93
+ const ext = extname(ctx.id)
94
+ const { parse, Lang } = await import('@ast-grep/napi')
95
+ let lang: Lang | undefined
96
+ if (/[cm]?tsx?/.test(ext)) {
97
+ lang = Lang.TypeScript
98
+ }
99
+ if (/[cm]?jsx?/.test(ext)) {
100
+ lang = Lang.JavaScript
59
101
  }
60
- })
102
+ if (/json?/.test(ext)) {
103
+ lang = Lang.Json
104
+ }
105
+ if (lang == null) return
106
+ const root = parse(lang, code).root()
107
+ allKeys.forEach(key => {
108
+ root
109
+ .findAll(key)
110
+ .forEach(node => {
111
+ const { start, end } = node.range()
112
+ const newValue = typeof allValues[key] === 'function'
113
+ ? allValues[key]({
114
+ ...ctx,
115
+ code,
116
+ mode: 'ast-grep',
117
+ node,
118
+ lang,
119
+ $: (input) => {
120
+ if (typeof input === 'string') {
121
+ return node.getMatch(input)?.text()
122
+ }
123
+ if ('raw' in input) {
124
+ return node.getMatch(input.raw[0])?.text()
125
+ }
126
+ }
127
+ })
128
+ : allValues[key]
129
+ if (([null, undefined, false] as unknown[]).includes(newValue)) return
130
+ ms.overwrite(
131
+ start.index,
132
+ end.index,
133
+ newValue as string
134
+ )
135
+ })
136
+ })
137
+ }
61
138
  return ms
62
139
  }
63
140
 
64
141
  return {
65
142
  name: 'jiek:replace',
143
+ buildStart() {
144
+ if (mode === 'ast-grep') {
145
+ try {
146
+ require.resolve('@ast-grep/napi')
147
+ this.warn(
148
+ 'You are using `ast-grep` mode, please make sure you have installed `@ast-grep/napi`'
149
+ )
150
+ } catch {
151
+ mode = 'string'
152
+ }
153
+ }
154
+ },
66
155
  transform: {
67
156
  order: 'pre',
68
- handler(code, id) {
157
+ async handler(code, id) {
69
158
  if (allKeys.length === 0) return
70
159
  if (filter(id)) return
71
- const ms = replaceAll({ type: 'transform', id }, code)
160
+ const ms = await replaceAll({ type: 'transform', id }, code)
72
161
  if (ms == null) return
73
162
 
74
163
  return {
@@ -79,11 +168,11 @@ export default definePlugin((options: Options = {}) => {
79
168
  },
80
169
  renderChunk: {
81
170
  order: 'post',
82
- handler(code, { fileName: id }) {
171
+ async handler(code, { fileName: id }) {
83
172
  if (allKeys.length === 0) return
84
173
  if (filter(id)) return
85
174
 
86
- const ms = replaceAll({ type: 'renderChunk', id }, code)
175
+ const ms = await replaceAll({ type: 'renderChunk', id }, code)
87
176
  if (ms == null) return
88
177
 
89
178
  return {