diff --git a/__tests__/frameworks/vite/__snapshots__/external.spec.ts.snap b/__tests__/frameworks/vite/__snapshots__/external.spec.ts.snap index 9be675f..2570737 100644 Binary files a/__tests__/frameworks/vite/__snapshots__/external.spec.ts.snap and b/__tests__/frameworks/vite/__snapshots__/external.spec.ts.snap differ diff --git a/src/loader-query.ts b/src/loader-query.ts index 5a521f0..91d0825 100644 --- a/src/loader-query.ts +++ b/src/loader-query.ts @@ -1,5 +1,3 @@ -import type { SFCPluginOptions } from './types' - export interface VueQuery { vue?: boolean type?: 'script' | 'template' | 'style' | 'custom' | 'fluent' @@ -33,7 +31,7 @@ export function parseVueRequest(id: string) { } } -export function isCustomBlock(query: VueQuery, options: SFCPluginOptions): boolean { +export function isCustomBlock(query: VueQuery, options: { blockType: string }): boolean { return ( 'vue' in query && (query.type === 'custom' // for vite (@vite-plugin-vue) diff --git a/src/plugins/external-plugin.ts b/src/plugins/external-plugin.ts index a11d1ed..d77f5da 100644 --- a/src/plugins/external-plugin.ts +++ b/src/plugins/external-plugin.ts @@ -5,34 +5,18 @@ import { createUnplugin } from 'unplugin' import MagicString from 'magic-string' import { createFilter, makeLegalIdentifier } from '@rollup/pluginutils' -import type { ExternalPluginOptions, InsertInfo } from '../types' +import type { ExternalPluginOptions } from '../types' +import { isCustomBlock, parseVueRequest } from '../loader-query' import { getSyntaxErrors } from './ftl/parse' -function getInsertInfo(source: string): InsertInfo { - let target = null - - // vite-plugin-vue2 - if (source.includes('__component__')) - target = '__component__' - - // rollup-plugin-vue - if (source.includes('export default script')) - target = 'script' - - // @vitejs/plugin-vue - if (source.includes('_sfc_main')) - target = '_sfc_main' - - // vue-loader - if (source.includes('__exports__')) - target = '__exports__' - - const insertPos = source.indexOf('export default') - - if (insertPos === -1 || target === null) - throw new Error('Could not parse vue component. This is the issue with unplugin-fluent-vue.\nPlease report this issue to the unplugin-fluent-vue repository.') +const isVue = createFilter(['**/*.vue']) +const isFtl = createFilter(['**/*.ftl']) - return { insertPos, target } +interface Dependency { + locale: string + ftlPath: string + relativeFtlPath: string + importVariable: string } async function fileExists(filename: string): Promise { @@ -49,17 +33,7 @@ function normalizePath(path: string) { return path.replace(/\\/g, '/') } -const isVue = createFilter(['**/*.vue']) -const isFtl = createFilter(['**/*.ftl']) - -interface Dependency { - locale: string - ftlPath: string - relativeFtlPath: string - importVariable: string -} - -export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) => { +export const unplugin = createUnplugin((options: ExternalPluginOptions) => { const resolvedOptions = { checkSyntax: true, virtualModuleName: 'virtual:ftl-for-file', @@ -76,38 +50,6 @@ export const unplugin = createUnplugin((options: ExternalPluginOptions, meta) => } } - const insertFtlImports = (magic: MagicString, translations: Dependency[]) => { - for (const dep of translations) - magic.prepend(`import ${dep.importVariable} from '${dep.relativeFtlPath}';\n`) - } - - const insertHotCode = (magic: MagicString, translations: Dependency[], target: string, insertPos: number) => { - const __HOT_API__ = meta.framework === 'webpack' ? 'import.meta.webpackHot' : 'import.meta.hot' - - magic.appendLeft(insertPos, ` -if (${__HOT_API__}) { - ${__HOT_API__}.accept([${translations.map(dep => `'${dep.relativeFtlPath}'`).join(', ')}], (mods) => { - ${translations.map(({ locale, importVariable }) => `${target}.fluent['${locale}'] = ${importVariable}`).join('\n')} - - if (mods) { - ${translations.map(({ locale }, index) => `if (mods['${index}']) ${target}.fluent['${locale}'] = mods['${index}'].default`).join('\n')} - } - - delete ${target}._fluent - if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') { - // Vue 3 - __VUE_HMR_RUNTIME__.reload(${target}.__hmrId, ${target}) - } else { - // Vue 2 - // There is no proper api to access HMR for component from custom block - // so use this magic - delete ${target}._Ctor - } - }) -} -`) - } - const getTranslationsForFile = async (id: string) => { const dependencies: Dependency[] = [] for (const locale of options.locales) { @@ -130,13 +72,21 @@ if (${__HOT_API__}) { return dependencies } + const isFluentCustomBlock = (id: string) => { + const request = parseVueRequest(id) + return isCustomBlock(request.query, { blockType: 'fluent' }) + } + return { name: 'unplugin-fluent-vue-external', - enforce: meta.framework === 'webpack' ? 'post' : undefined, + enforce: 'pre', resolveId(id, importer) { if (id === resolvedOptions.virtualModuleName) return `${id}?importer=${importer}` }, + loadInclude(id: string) { + return id.startsWith(resolvedOptions.virtualModuleName) + }, async load(id) { if (!id.startsWith(resolvedOptions.virtualModuleName)) return @@ -159,29 +109,19 @@ if (${__HOT_API__}) { return code }, transformInclude(id: string) { - return isVue(id) || isFtl(id) + return isVue(id) || isFtl(id) || isFluentCustomBlock(id) }, async transform(source: string, id: string) { if (isVue(id)) { const magic = new MagicString(source, { filename: id }) - const { insertPos, target } = getInsertInfo(source) - const translations = await getTranslationsForFile(id) if (translations.length === 0) return - for (const { ftlPath } of translations) - this.addWatchFile(ftlPath) - - insertFtlImports(magic, translations) - - magic.appendLeft(insertPos, `${target}.fluent = ${target}.fluent || {};\n`) - for (const dep of translations) - magic.appendLeft(insertPos, `${target}.fluent['${dep.locale}'] = ${dep.importVariable}\n`) - - insertHotCode(magic, translations, target, insertPos) + for (const { relativeFtlPath, locale } of translations) + magic.append(`\n`) return { code: magic.toString(), @@ -198,10 +138,28 @@ if (${__HOT_API__}) { return ` import { FluentResource } from '@fluent/bundle' -export default new FluentResource(${JSON.stringify(source)}) +export default /*#__PURE__*/ new FluentResource(${JSON.stringify(source)}) ` } + const query = parseVueRequest(id).query + if (isFluentCustomBlock(id)) { + if (options.checkSyntax) { + const errorsText = getSyntaxErrors(source) + if (errorsText) + this.error(errorsText) + } + + return ` +import { FluentResource } from '@fluent/bundle' + +export default function (Component) { + const target = Component.options || Component + target.fluent = target.fluent || {} + target.fluent['${query.locale}'] = new FluentResource(${JSON.stringify(source)}) +}` + } + return undefined }, } diff --git a/src/types.ts b/src/types.ts index e87391a..ccf3eb8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,8 +19,3 @@ export interface SFCPluginOptions { blockType?: string checkSyntax?: boolean } - -export interface InsertInfo { - insertPos: number - target: string -}