diff --git a/packages/vue/package.json b/packages/vue/package.json index 61a895ae8..1c175bd3a 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -72,6 +72,16 @@ "default": "./dist/extractor/index.mjs" } }, + "./vite-plugin": { + "require": { + "types": "./dist/vite-plugin/index.d.cts", + "default": "./dist/vite-plugin/index.cjs" + }, + "import": { + "types": "./dist/vite-plugin/index.d.mts", + "default": "./dist/vite-plugin/index.mjs" + } + }, "./package.json": "./package.json" }, "files": [ @@ -87,13 +97,20 @@ "@lingui/cli": "4.8.0-next.1", "@lingui/core": "4.8.0-next.1", "@lingui/message-utils": "4.8.0-next.1", + "@lingui/vite-plugin": "4.8.0-next.1", "@vue/compiler-core": "^3.3.4", "@vue/compiler-sfc": "^3.3.4", "vue": "^3.3.4" }, + "peerDependencies": { + "@vitejs/plugin-vue": "*", + "vite": "^3 || ^4 || ^5.0.9" + }, "devDependencies": { "@lingui/conf": "4.8.0-next.1", "@types/babel__core": "^7.20.5", - "unbuild": "2.0.0" + "@vitejs/plugin-vue": "^5.0.5", + "unbuild": "2.0.0", + "vite": "4.1.4" } } diff --git a/packages/vue/src/compiler/index.ts b/packages/vue/src/compiler/index.ts index c68e1aa95..6f1f27660 100644 --- a/packages/vue/src/compiler/index.ts +++ b/packages/vue/src/compiler/index.ts @@ -1,2 +1 @@ -export { babelMacros } from "./babel-macros" export { transformer } from "./transformer" diff --git a/packages/vue/src/compiler/transformTrans.ts b/packages/vue/src/compiler/transformTrans.ts index ab15d36e6..c1f53149d 100644 --- a/packages/vue/src/compiler/transformTrans.ts +++ b/packages/vue/src/compiler/transformTrans.ts @@ -1,7 +1,6 @@ import { createSimpleExpression, type ElementNode, - type TransformContext, NodeTypes, ConstantTypes, ElementTypes, @@ -133,7 +132,9 @@ function convertLoc( export function transformTrans( node: ElementNode, - transformContext: TransformContext + options: { + stripNonEssentialProps?: boolean + } ) { const loc = node.loc const tokens = tokenizeTrans( @@ -144,7 +145,7 @@ export function transformTrans( } return identifier.name === macro - }, false) + }, !!options.stripNonEssentialProps) ) const messageFormat = new ICUMessageFormat() diff --git a/packages/vue/src/compiler/transformVt.ts b/packages/vue/src/compiler/transformVt.ts index ab1e07a25..b225aee48 100644 --- a/packages/vue/src/compiler/transformVt.ts +++ b/packages/vue/src/compiler/transformVt.ts @@ -36,14 +36,14 @@ function createLinguiSimpleExpression( ) } -function createVueMacroContext() { +function createVueMacroContext(options: { stripNonEssentialProps?: boolean }) { return createMacroJsContext((identifier, macro) => { if (macro === JsMacroName.t) { return identifier.name === "vt" } return identifier.name === macro - }, false) + }, !!options?.stripNonEssentialProps) } export function tokenizeAsChoiceComponentOrUndefined( @@ -74,8 +74,11 @@ export function tokenizeAsLinguiTemplateLiteralOrUndefined( export function transformVt( vueNode: ExpressionNode, - ctx = createVueMacroContext() + options: { + stripNonEssentialProps?: boolean + } ) { + const ctx = createVueMacroContext(options) if (!vueNode.ast) { return vueNode } diff --git a/packages/vue/src/compiler/transformer.ts b/packages/vue/src/compiler/transformer.ts index 9d33ba3cb..4789916ce 100644 --- a/packages/vue/src/compiler/transformer.ts +++ b/packages/vue/src/compiler/transformer.ts @@ -9,6 +9,10 @@ import { import { transformVt } from "./transformVt" import { transformTrans } from "./transformTrans" +export type LinguiTransformerOptions = { + stripNonEssentialProps?: boolean +} + /** * Here is an entry point transformer. * We need our template transformer to operate on user authored code @@ -17,32 +21,43 @@ import { transformTrans } from "./transformTrans" * So this transformer unshifts a real transformer to the transformers array. * */ -export const transformer: NodeTransform = (node, context) => { - if ( - node.type === NodeTypes.ROOT && - !context.nodeTransforms.includes(templateTransformer) - ) { - context.nodeTransforms.unshift(templateTransformer) +export const transformer = + (options: LinguiTransformerOptions): NodeTransform => + (node, context) => { + if ( + node.type === NodeTypes.ROOT && + !context.nodeTransforms.find( + (t) => (t as any).__name === "linguiTransform" + ) + ) { + context.nodeTransforms.unshift(templateTransformer(options)) + } } -} /** * Actual transformer expanding macro calls. */ -const templateTransformer: NodeTransform = (node, context) => { - if (isElementNode(node)) { - if (isTrans(node)) { - transformTrans(node, context) - } +const templateTransformer = ( + options: LinguiTransformerOptions +): NodeTransform => { + const transformer: NodeTransform = (node) => { + if (isElementNode(node)) { + if (isTrans(node)) { + transformTrans(node, options) + } - for (const prop of node.props) { - if (isDirectiveNode(prop) && prop.exp) { - prop.exp = transformVt(prop.exp) + for (const prop of node.props) { + if (isDirectiveNode(prop) && prop.exp) { + prop.exp = transformVt(prop.exp, options) + } } } - } - if (isInterpolationNode(node)) { - node.content = transformVt(node.content) + if (isInterpolationNode(node)) { + node.content = transformVt(node.content, options) + } } + + ;(transformer as any).__name = "linguiTransform" + return transformer } diff --git a/packages/vue/src/plugins/lingui.ts b/packages/vue/src/plugins/lingui.ts index 25b977d50..f2a03911d 100644 --- a/packages/vue/src/plugins/lingui.ts +++ b/packages/vue/src/plugins/lingui.ts @@ -1,8 +1,6 @@ import { type I18n } from "@lingui/core" import { inject, type Plugin } from "vue" -// - type LinguiPluginOptions = { i18n: I18n } diff --git a/packages/vue/src/compiler/babel-macros.ts b/packages/vue/src/vite-plugin/core-macros-plugin.ts similarity index 52% rename from packages/vue/src/compiler/babel-macros.ts rename to packages/vue/src/vite-plugin/core-macros-plugin.ts index 66a3f0bef..2b48e13fd 100644 --- a/packages/vue/src/compiler/babel-macros.ts +++ b/packages/vue/src/vite-plugin/core-macros-plugin.ts @@ -1,14 +1,16 @@ -// TODO: need to go full ESM to be able to use those types -// import { type Plugin, type TransformResult } from 'vite' +import { type Plugin, type TransformResult } from "vite" import * as babel from "@babel/core" const sourceRegex = /\.(:?[j|t]sx?|vue)$/u // make babel macros works in vite -export function babelMacros() { +export function linguiCoreMacros(): Plugin { return { - name: "vite-plugin-babel-macros", - async transform(source: string, filename: string) { + name: "vite-plugin-vue-lingui-babel-macro", + async transform( + source: string, + filename: string + ): Promise { if (filename.includes("node_modules")) { return undefined } @@ -19,13 +21,16 @@ export function babelMacros() { const result = await babel.transformAsync(source, { filename, - plugins: ["macros"], + plugins: ["@lingui/babel-plugin-lingui-macro"], babelrc: false, configFile: false, sourceMaps: true, }) - return result + return { + code: result?.code, + map: result?.map, + } as TransformResult }, } as const } diff --git a/packages/vue/src/vite-plugin/index.ts b/packages/vue/src/vite-plugin/index.ts new file mode 100644 index 000000000..ee2c25731 --- /dev/null +++ b/packages/vue/src/vite-plugin/index.ts @@ -0,0 +1,61 @@ +import { PluginOption } from "vite" +import { lingui } from "@lingui/vite-plugin" +import { linguiCoreMacros } from "./core-macros-plugin" +import type { Api } from "@vitejs/plugin-vue" +import { transformer } from "../compiler" + +type LinguiConfigOpts = { + cwd?: string + configPath?: string + skipValidation?: boolean +} + +type Options = { + suppressRegisteringBabelMacro?: boolean + linguiConfigOptions?: LinguiConfigOpts + isProduction?: boolean +} + +export function vueLingui(options: Options = {}): PluginOption[] { + options = { + suppressRegisteringBabelMacro: false, + isProduction: process.env.NODE_ENV === "production", + ...options, + } + + return [ + lingui(options.linguiConfigOptions), + !options.suppressRegisteringBabelMacro ? linguiCoreMacros() : false, + { + name: "vite-plugin-lingui-vue-transform", + enforce: "pre", + configResolved(config) { + const vitePlugin = config.plugins.find( + (plugin) => plugin.name === "vite:vue" + ) + + if (!vitePlugin) { + throw new Error( + "Lingui Vue Plugin: Vite plugin is not found in your configuration. Please install it and to your Vite config" + ) + } + + const api = vitePlugin.api as Api + + // register Lingui template transformer + api.options.template = { + ...api.options.template, + compilerOptions: { + ...api.options.template?.compilerOptions, + nodeTransforms: [ + transformer({ stripNonEssentialProps: options.isProduction }), + ...(api.options.template?.compilerOptions?.nodeTransforms || []), + ], + }, + } + }, + }, + ] +} + +export default lingui diff --git a/yarn.lock b/yarn.lock index 562f12f8a..4295c824e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2989,7 +2989,7 @@ __metadata: languageName: unknown linkType: soft -"@lingui/vite-plugin@workspace:packages/vite-plugin": +"@lingui/vite-plugin@4.8.0-next.1, @lingui/vite-plugin@workspace:packages/vite-plugin": version: 0.0.0-use.local resolution: "@lingui/vite-plugin@workspace:packages/vite-plugin" dependencies: @@ -3017,11 +3017,17 @@ __metadata: "@lingui/conf": 4.8.0-next.1 "@lingui/core": 4.8.0-next.1 "@lingui/message-utils": 4.8.0-next.1 + "@lingui/vite-plugin": 4.8.0-next.1 "@types/babel__core": ^7.20.5 + "@vitejs/plugin-vue": ^5.0.5 "@vue/compiler-core": ^3.3.4 "@vue/compiler-sfc": ^3.3.4 unbuild: 2.0.0 + vite: 4.1.4 vue: ^3.3.4 + peerDependencies: + "@vitejs/plugin-vue": "*" + vite: ^3 || ^4 || ^5.0.9 languageName: unknown linkType: soft @@ -4487,6 +4493,16 @@ __metadata: languageName: node linkType: hard +"@vitejs/plugin-vue@npm:^5.0.5": + version: 5.0.5 + resolution: "@vitejs/plugin-vue@npm:5.0.5" + peerDependencies: + vite: ^5.0.0 + vue: ^3.2.25 + checksum: 96077539da0c6d0d19f68ca112466d804e374a9e18cdb97b5396793a7eecaff433c109bac82ff0d130d39c48963f658158f2f44696fd0a50df2eb5080fafbdcc + languageName: node + linkType: hard + "@vue/compiler-core@npm:3.2.47": version: 3.2.47 resolution: "@vue/compiler-core@npm:3.2.47"