diff --git a/packages/esbuild-plugin-ast-vue/README.md b/packages/esbuild-plugin-ast-vue/README.md
index a640cf1..4515c98 100644
--- a/packages/esbuild-plugin-ast-vue/README.md
+++ b/packages/esbuild-plugin-ast-vue/README.md
@@ -1,23 +1,24 @@
-# esbuild-plugin-ast
+# esbuild-plugin-ast-vue
-A plugin to generate an AST representation of your `.js` files. The plugin use [Acorn](https://github.com/acornjs/acorn) to produce an `estree` compliant `AST` object. You can then apply transformations by providing a `visitor` object.
+A plugin to generate an AST representation of your `.vue` files. The plugin use [Acorn](https://github.com/acornjs/acorn) to produce an `estree` compliant `AST` object. You can then apply transformations by providing a `visitor` object. In order to also parse your `.js` dependencies imported in your scripts, you should probably use the [@liip/esbuild-plugin-ast](https://github.com/liip/class-prefixer/tree/main/packages/esbuild-plugin-ast) in conjunction with this plugin since `esbuild` does not allow plugin composition.
## Installation
```
-npm i -D @liip/esbuild-plugin-ast
+npm i -D @liip/esbuild-plugin-ast-vue @liip/esbuild-plugin-ast
```
## Usage
```javascript
import { astParser } from '@liip/esbuild-plugin-ast';
+import { astParserVue } from '@liip/esbuild-plugin-ast-vue';
...
await esbuild.context({
...
- plugins: [astParser(options)],
+ plugins: [astParserVue(vueParserOptions), astParser(parserOptions)],
...
});
@@ -31,6 +32,7 @@ You can configure the way the plugin works by setting different options.
```typescript
interface AstParserVueOptions extends AstParserOptions {
+ scriptNamespace?: string;
templateVisitor: AstParserOptions['visitor'];
templateOptions?: Pick<
SFCTemplateCompileOptions,
@@ -52,6 +54,10 @@ interface AstParserVueOptions extends AstParserOptions {
}
```
+### scriptNamespace
+
+A string namespace used to tell `@liip/esbuild-plugin-ast` to parse `.js` dependencies from your `.vue` files. This is required since `esbuild` does not allow plugin composition. This namespace should also be provided to `astParser` in order to work correctly.
+
### visitor
An `ESTraverse.Visitor` object used to apply AST transformations. Check the [Estraverse documentation](https://github.com/estools/estraverse) form more information on the available API.
diff --git a/packages/esbuild-plugin-ast-vue/package.json b/packages/esbuild-plugin-ast-vue/package.json
index cc6a789..fc87838 100644
--- a/packages/esbuild-plugin-ast-vue/package.json
+++ b/packages/esbuild-plugin-ast-vue/package.json
@@ -48,6 +48,7 @@
"hash-sum": "^2.0.0"
},
"peerDependencies": {
+ "@liip/esbuild-plugin-ast": "0.3.x",
"esbuild": "0.19.x"
}
}
diff --git a/packages/esbuild-plugin-ast-vue/src/__tests__/plugin.spec.ts b/packages/esbuild-plugin-ast-vue/src/__tests__/plugin.spec.ts
index 89696ff..77f3378 100644
--- a/packages/esbuild-plugin-ast-vue/src/__tests__/plugin.spec.ts
+++ b/packages/esbuild-plugin-ast-vue/src/__tests__/plugin.spec.ts
@@ -2,6 +2,7 @@ import { mkdtemp, writeFile, rm, mkdir } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
+import { astParser, AstParserOptions } from '@liip/esbuild-plugin-ast';
import { context, BuildContext, BuildOptions, OutputFile } from 'esbuild';
import { astParserVue, AstParserVueOptions } from '../plugin';
@@ -10,20 +11,23 @@ describe('astPluginVue', () => {
const placeholder = 'astPluginVue';
const argument = 'ast-plugin-vue';
const virtualPackage = 'ast-plugin-vue';
-
- const pluginOptions: AstParserVueOptions = {
- visitors: {
- enter(node) {
- if (
- node.type === 'CallExpression' &&
- node.callee.type === 'Identifier' &&
- node.callee.name === placeholder
- ) {
- return node.arguments[0];
- }
- return node;
- },
+ const namespace = 'ast-parser-vue';
+
+ const visitors: AstParserOptions['visitors'] = {
+ enter(node) {
+ if (
+ node.type === 'CallExpression' &&
+ node.callee.type === 'Identifier' &&
+ node.callee.name === placeholder
+ ) {
+ return node.arguments[0];
+ }
+ return node;
},
+ };
+
+ const vuePluginOptions: AstParserVueOptions = {
+ visitors,
templateVisitor: {
enter(node) {
if (
@@ -50,7 +54,6 @@ describe('astPluginVue', () => {
minify: false,
write: false,
external: ['vue'],
- plugins: [astParserVue(pluginOptions)],
};
let tmpDir: string;
@@ -76,12 +79,6 @@ describe('astPluginVue', () => {
entryPoint,
`import testCase from './test-case.vue';\ntestCase();`,
);
-
- ctx = await context({
- ...esbuildConfig,
- entryPoints: { index: entryPoint },
- minify: true,
- });
});
afterEach(async () => {
@@ -93,49 +90,103 @@ describe('astPluginVue', () => {
await rm(tmpDir, { recursive: true, force: true });
});
- describe('Script section', () => {
- it('should correctly handle Vue dependencies containing astPlugin placeholder', async () => {
- const testCase = ``;
- await writeFile(testFile, testCase);
-
- const result = await ctx.rebuild();
-
- expect((result.outputFiles as OutputFile[])[0].text).toEqual(
- expect.stringContaining(argument),
- );
- expect((result.outputFiles as OutputFile[])[0].text).toEqual(
- expect.not.stringContaining(placeholder),
- );
+ describe('Standalone usage', () => {
+ beforeEach(async () => {
+ ctx = await context({
+ ...esbuildConfig,
+ plugins: [astParserVue(vuePluginOptions)],
+ entryPoints: { index: entryPoint },
+ minify: true,
+ });
});
- it('should correctly handle Vue dependencies containing astPlugin placeholder in script setup', async () => {
- const testCase = ``;
- await writeFile(testFile, testCase);
+ describe('Script section', () => {
+ it('should correctly handle Vue dependencies containing astPlugin placeholder', async () => {
+ const testCase = ``;
+ await writeFile(testFile, testCase);
+
+ const result = await ctx.rebuild();
+
+ expect((result.outputFiles as OutputFile[])[0].text).toEqual(
+ expect.stringContaining(argument),
+ );
+ expect((result.outputFiles as OutputFile[])[0].text).toEqual(
+ expect.not.stringContaining(placeholder),
+ );
+ });
+
+ it('should correctly handle Vue dependencies containing astPlugin placeholder in script setup', async () => {
+ const testCase = ``;
+ await writeFile(testFile, testCase);
+
+ const result = await ctx.rebuild();
+
+ expect((result.outputFiles as OutputFile[])[0].text).toEqual(
+ expect.stringContaining(argument),
+ );
+ expect((result.outputFiles as OutputFile[])[0].text).toEqual(
+ expect.not.stringContaining(placeholder),
+ );
+ });
+
+ describe('Template section', () => {
+ it('should correctly handle Vue dependencies with static class in the template', async () => {
+ const testCase = ``;
+ await writeFile(testFile, testCase);
+
+ const result = await ctx.rebuild();
+
+ expect((result.outputFiles as OutputFile[])[0].text).toEqual(
+ expect.stringContaining(`class:"${argument}"`),
+ );
+ expect((result.outputFiles as OutputFile[])[0].text).toEqual(
+ expect.not.stringContaining(placeholder),
+ );
+ });
+ });
+ });
+ });
- const result = await ctx.rebuild();
+ describe('Combined with esbuild-plugin-ast usage', () => {
+ beforeEach(async () => {
+ const pluginOptions: AstParserOptions = {
+ namespace,
+ visitors,
+ };
+
+ ctx = await context({
+ ...esbuildConfig,
+ plugins: [
+ astParserVue({ scriptNamespace: namespace, ...vuePluginOptions }),
+ astParser(pluginOptions),
+ ],
+ entryPoints: { index: entryPoint },
+ minify: true,
+ });
+ });
- expect((result.outputFiles as OutputFile[])[0].text).toEqual(
- expect.stringContaining(argument),
+ it('should correctly handle dependencies containing astPlugin placeholder in script', async () => {
+ const testDepFile = join(
+ tmpDir,
+ `./packages/${virtualPackage}/test-dep.js`,
);
- expect((result.outputFiles as OutputFile[])[0].text).toEqual(
- expect.not.stringContaining(placeholder),
- );
- });
- });
- describe('Template section', () => {
- it('should correctly handle Vue dependencies with static class in the template', async () => {
- const testCase = ``;
+ const testDep = `export function test() { return ${placeholder}('${argument}'); }`;
+ const testCase = ``;
+
+ await writeFile(testDepFile, testDep);
await writeFile(testFile, testCase);
const result = await ctx.rebuild();
expect((result.outputFiles as OutputFile[])[0].text).toEqual(
- expect.stringContaining(`class:"${argument}"`),
+ expect.stringContaining(argument),
);
expect((result.outputFiles as OutputFile[])[0].text).toEqual(
expect.not.stringContaining(placeholder),
);
+
+ await rm(testDepFile);
});
});
});
diff --git a/packages/esbuild-plugin-ast-vue/src/plugin.ts b/packages/esbuild-plugin-ast-vue/src/plugin.ts
index 2477e9e..c5c502e 100644
--- a/packages/esbuild-plugin-ast-vue/src/plugin.ts
+++ b/packages/esbuild-plugin-ast-vue/src/plugin.ts
@@ -17,7 +17,8 @@ import { resolvePath, validateDependency } from './utils';
type ExtractNonArray = T extends Array ? never : T;
-export interface AstParserVueOptions extends AstParserOptions {
+export interface AstParserVueOptions
+ extends Omit {
templateOptions?: Pick<
SFCTemplateCompileOptions,
| 'compiler'
@@ -36,8 +37,11 @@ export interface AstParserVueOptions extends AstParserOptions {
| 'postcssPlugins'
>;
templateVisitor: ExtractNonArray;
+ scriptNamespace?: string;
}
+type ScriptResolvedReturn = { path: string; namespace?: string };
+
validateDependency();
export function astParserVue({
@@ -46,6 +50,7 @@ export function astParserVue({
styleOptions,
visitors,
templateVisitor,
+ scriptNamespace,
}: AstParserVueOptions): Plugin {
return {
name: 'astParserVue',
@@ -73,9 +78,15 @@ export function astParserVue({
* them in a specific namespace
*/
build.onResolve({ filter: /\.vue\?type=script/ }, (args) => {
- return {
+ const resolved: ScriptResolvedReturn = {
path: args.path,
};
+
+ if (scriptNamespace) {
+ resolved.namespace = scriptNamespace;
+ }
+
+ return resolved;
});
build.onLoad({ filter: /\.vue\?type=script/ }, (args) => {
diff --git a/packages/esbuild-plugin-ast/src/plugin.ts b/packages/esbuild-plugin-ast/src/plugin.ts
index ca25500..2e052bc 100644
--- a/packages/esbuild-plugin-ast/src/plugin.ts
+++ b/packages/esbuild-plugin-ast/src/plugin.ts
@@ -10,16 +10,17 @@ import type { Plugin, OnResolveArgs } from 'esbuild';
export interface AstParserOptions {
dependencies?: string[];
visitors: Visitor | Visitor[];
+ namespace?: string;
}
export function astParser({
dependencies,
visitors,
+ namespace = 'ast-parser',
}: AstParserOptions): Plugin {
return {
name: 'astParser',
setup(build) {
- const namespace = 'ast-parser';
const excludeFileTypes = Object.keys(
build.initialOptions.loader || {},
).map((key) => key.slice(1));
@@ -109,7 +110,7 @@ export function astParser({
/**
* Load, parse and modify any other imports that was placed in
- * the `phx-class-prefixer` namespace
+ * the `ast-parser` namespace
*/
build.onLoad({ filter: /.*/, namespace }, async (args) => {
const source = await readFile(args.path, 'utf-8');