diff --git a/package.json b/package.json index 410974a..199617e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "bugs": { "url": "https://github.com/inlitify/bundle-tools/issues" }, - "packageManager": "yarn@3.1.1", "changelog": { "labels": { "Type: Feature": ":star: Features", diff --git a/packages/bundle-utils/package.json b/packages/bundle-utils/package.json index fca4751..fd6f315 100644 --- a/packages/bundle-utils/package.json +++ b/packages/bundle-utils/package.json @@ -18,6 +18,9 @@ } }, "dependencies": { + "@babel/parser": "^7.21.2", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2", "@intlify/message-compiler": "next", "@intlify/shared": "next", "jsonc-eslint-parser": "^1.0.1", diff --git a/packages/bundle-utils/src/codegen.ts b/packages/bundle-utils/src/codegen.ts index a2d498a..b78b6af 100644 --- a/packages/bundle-utils/src/codegen.ts +++ b/packages/bundle-utils/src/codegen.ts @@ -31,7 +31,7 @@ export interface SourceLocationable { loc?: { start: Position end: Position - } + } | null } /** diff --git a/packages/bundle-utils/src/js.ts b/packages/bundle-utils/src/js.ts new file mode 100644 index 0000000..ff2df1f --- /dev/null +++ b/packages/bundle-utils/src/js.ts @@ -0,0 +1,401 @@ +/** + * Code generator for i18n js resource + */ + +import { parse as parseJavaScript, ParseResult } from '@babel/parser' +import { default as traverseNodes } from '@babel/traverse' +import { + createCodeGenerator, + generateMessageFunction, + mapLinesColumns +} from './codegen' +import { RawSourceMap } from 'source-map' + +import type { + File, + Node, + BooleanLiteral, + NullLiteral, + NumericLiteral, + StringLiteral, + TemplateLiteral +} from '@babel/types' +import type { NodePath } from '@babel/traverse' +import type { CodeGenOptions, CodeGenerator, CodeGenResult } from './codegen' + +/** + * @internal + */ +export function generate( + targetSource: string | Buffer, + { + type = 'plain', + bridge = false, + exportESM = false, + filename = 'vue-i18n-loader.js', + inSourceMap = undefined, + locale = '', + isGlobal = false, + sourceMap = false, + env = 'development', + forceStringify = false, + onError = undefined, + useClassComponent = false + }: CodeGenOptions, + injector?: () => string +): CodeGenResult> { + const target = Buffer.isBuffer(targetSource) + ? targetSource.toString() + : targetSource + const value = target + + const options = { + type, + bridge, + exportESM, + source: value, + sourceMap, + locale, + isGlobal, + inSourceMap, + env, + filename, + forceStringify, + onError, + useClassComponent + } as CodeGenOptions + const generator = createCodeGenerator(options) + + const ast = parseJavaScript(value, { + sourceType: 'module', + sourceFilename: filename, + allowImportExportEverywhere: true + }) + + if (ast.errors) { + // TODO: + } + + const codeMaps = generateNode(generator, ast as File, options, injector) + + const { code, map } = generator.context() + // if (map) { + // const s = new SourceMapConsumer((map as any).toJSON()) + // s.eachMapping(m => { + // console.log('sourcemap json', m) + // }) + // } + // prettier-ignore + const newMap = map + ? mapLinesColumns((map as any).toJSON(), codeMaps, inSourceMap) || null // eslint-disable-line @typescript-eslint/no-explicit-any + : null + return { + ast: ast as ParseResult, + code, + map: newMap != null ? newMap : undefined + } +} + +function generateNode( + generator: CodeGenerator, + node: File, + options: CodeGenOptions, + injector?: () => string +): Map { + const propsCountStack = [] as number[] + const pathStack = [] as string[] + const itemsCountStack = [] as number[] + const skipStack = [] as boolean[] + const { forceStringify } = generator.context() + const codeMaps = new Map() + const { + type, + bridge, + exportESM, + sourceMap, + isGlobal, + locale, + useClassComponent + } = options + + // prettier-ignore + const componentNamespace = bridge + ? `Component.options` + : useClassComponent + ? `Component.__o` + : `Component` + + // @ts-ignore TODO: `tranverse` first argument type should be fixed + traverseNodes(node as Node, { + enter({ node, parent }: NodePath) { + switch (node.type) { + case 'Program': + if (type === 'plain') { + generator.push(`export default `) + } else if (type === 'sfc') { + // for 'sfc' + const variableName = + type === 'sfc' ? (!isGlobal ? '__i18n' : '__i18nGlobal') : '' + const localeName = + type === 'sfc' ? (locale != null ? locale : `""`) : '' + const exportSyntax = bridge + ? exportESM + ? `export default` + : `module.exports =` + : `export default` + generator.push(`${exportSyntax} function (Component) {`) + generator.indent() + generator.pushline( + `${componentNamespace}.${variableName} = ${componentNamespace}.${variableName} || []` + ) + generator.push(`${componentNamespace}.${variableName}.push({`) + generator.indent() + generator.pushline(`"locale": ${JSON.stringify(localeName)},`) + generator.push(`"resource": `) + } + break + case 'ObjectExpression': + generator.push(`{`) + generator.indent() + propsCountStack.push(node.properties.length) + if (parent.type === 'ArrayExpression') { + const lastIndex = itemsCountStack.length - 1 + const currentCount = + parent.elements.length - itemsCountStack[lastIndex] + pathStack.push(currentCount.toString()) + itemsCountStack[lastIndex] = --itemsCountStack[lastIndex] + } + break + case 'ObjectProperty': + if ( + isJSONablePrimitiveLiteral(node.value) && + (node.key.type === 'StringLiteral' || + node.key.type === 'Identifier') + ) { + // prettier-ignore + const name = node.key.type === 'StringLiteral' + ? node.key.value + : node.key.name + if ( + node.value.type === 'StringLiteral' || + node.value.type === 'TemplateLiteral' + ) { + const value = getValue(node.value) as string + generator.push(`${JSON.stringify(name)}: `) + pathStack.push(name) + const { code, map } = generateMessageFunction( + value, + options, + pathStack + ) + sourceMap && map != null && codeMaps.set(value, map) + generator.push(`${code}`, node.value, value) + skipStack.push(false) + } else { + const value = getValue(node.value) + if (forceStringify) { + const strValue = JSON.stringify(value) + generator.push(`${JSON.stringify(name)}: `) + pathStack.push(name) + const { code, map } = generateMessageFunction( + strValue, + options, + pathStack + ) + sourceMap && map != null && codeMaps.set(strValue, map) + generator.push(`${code}`, node.value, strValue) + } else { + generator.push( + `${JSON.stringify(name)}: ${JSON.stringify(value)}` + ) + pathStack.push(name) + } + skipStack.push(false) + } + } else if ( + (node.value.type === 'ObjectExpression' || + node.value.type === 'ArrayExpression') && + (node.key.type === 'StringLiteral' || + node.key.type === 'Identifier') + ) { + // prettier-ignore + const name = node.key.type === 'StringLiteral' + ? node.key.value + : node.key.name + generator.push(`${JSON.stringify(name)}: `) + pathStack.push(name) + } else { + // for Regex, function, etc. + skipStack.push(true) + } + const lastIndex = propsCountStack.length - 1 + propsCountStack[lastIndex] = --propsCountStack[lastIndex] + break + case 'ArrayExpression': + generator.push(`[`) + generator.indent() + if (parent.type === 'ArrayExpression') { + const lastIndex = itemsCountStack.length - 1 + const currentCount = + parent.elements.length - itemsCountStack[lastIndex] + pathStack.push(currentCount.toString()) + itemsCountStack[lastIndex] = --itemsCountStack[lastIndex] + } + itemsCountStack.push(node.elements.length) + break + default: + if (isJSONablePrimitiveLiteral(node)) { + if (parent.type === 'ArrayExpression') { + const lastIndex = itemsCountStack.length - 1 + const currentCount = + parent.elements.length - itemsCountStack[lastIndex] + pathStack.push(currentCount.toString()) + if ( + node.type === 'StringLiteral' || + node.type === 'TemplateLiteral' + ) { + const value = getValue(node) as string + const { code, map } = generateMessageFunction( + value, + options, + pathStack + ) + sourceMap && map != null && codeMaps.set(value, map) + generator.push(`${code}`, node, value) + skipStack.push(false) + } else { + const value = getValue(node) + if (forceStringify) { + const strValue = JSON.stringify(value) + const { code, map } = generateMessageFunction( + strValue, + options, + pathStack + ) + sourceMap && map != null && codeMaps.set(strValue, map) + generator.push(`${code}`, node, strValue) + } else { + generator.push(`${JSON.stringify(value)}`) + } + skipStack.push(false) + } + itemsCountStack[lastIndex] = --itemsCountStack[lastIndex] + } + } else { + // ... + } + break + } + }, + exit({ node, parent }: NodePath) { + switch (node.type) { + case 'Program': + if (type === 'sfc') { + generator.deindent() + generator.push(`})`) + if (bridge && injector) { + generator.newline() + generator.pushline( + `${componentNamespace}.__i18nBridge = ${componentNamespace}.__i18nBridge || []` + ) + generator.pushline( + `${componentNamespace}.__i18nBridge.push('${injector()}')` + ) + generator.pushline(`delete ${componentNamespace}._Ctor`) + } + generator.deindent() + generator.pushline(`}`) + } + break + case 'ObjectExpression': + if (propsCountStack[propsCountStack.length - 1] === 0) { + pathStack.pop() + propsCountStack.pop() + } + generator.deindent() + generator.push(`}`) + if (parent.type === 'ArrayExpression') { + if (itemsCountStack[itemsCountStack.length - 1] !== 0) { + pathStack.pop() + generator.pushline(`,`) + } + } + break + case 'ObjectProperty': + if (propsCountStack[propsCountStack.length - 1] !== 0) { + pathStack.pop() + if (!skipStack.pop()) { + generator.pushline(`,`) + } + } + break + case 'ArrayExpression': + if (itemsCountStack[itemsCountStack.length - 1] === 0) { + pathStack.pop() + itemsCountStack.pop() + } + generator.deindent() + generator.push(`]`) + if (parent.type === 'ArrayExpression') { + if (itemsCountStack[itemsCountStack.length - 1] !== 0) { + pathStack.pop() + if (!skipStack.pop()) { + generator.pushline(`,`) + } + } + } + break + default: + if (isJSONablePrimitiveLiteral(node)) { + if (parent.type === 'ArrayExpression') { + if (itemsCountStack[itemsCountStack.length - 1] !== 0) { + pathStack.pop() + if (!skipStack.pop()) { + generator.pushline(`,`) + } + } else { + if (!skipStack.pop()) { + generator.pushline(`,`) + } + } + } + } else { + // ... + } + break + } + } + }) + + return codeMaps +} + +type JSONablePrimitiveLiteral = + | NullLiteral + | BooleanLiteral + | NumericLiteral + | StringLiteral + | TemplateLiteral + +function isJSONablePrimitiveLiteral( + node: Node +): node is JSONablePrimitiveLiteral { + return ( + node.type === 'NullLiteral' || + node.type === 'BooleanLiteral' || + node.type === 'NumericLiteral' || + node.type === 'StringLiteral' || + node.type === 'TemplateLiteral' + ) +} + +function getValue(node: JSONablePrimitiveLiteral) { + // prettier-ignore + return node.type === 'StringLiteral' + ? node.value + : node.type === 'NullLiteral' + ? null + : node.type === 'TemplateLiteral' + ? node.quasis.map(quasi => quasi.value.cooked).join('') + : node.value +} diff --git a/packages/bundle-utils/test/fixtures/bare.js b/packages/bundle-utils/test/fixtures/bare.js new file mode 100644 index 0000000..abd5a8f --- /dev/null +++ b/packages/bundle-utils/test/fixtures/bare.js @@ -0,0 +1,3 @@ +export default { + hello: 'hello world!' +} diff --git a/packages/bundle-utils/test/fixtures/codegen/array-basic.js b/packages/bundle-utils/test/fixtures/codegen/array-basic.js new file mode 100644 index 0000000..200ce11 --- /dev/null +++ b/packages/bundle-utils/test/fixtures/codegen/array-basic.js @@ -0,0 +1,3 @@ +export default { + foo: [['bar'], ['baz']] +} diff --git a/packages/bundle-utils/test/fixtures/codegen/array-mix.js b/packages/bundle-utils/test/fixtures/codegen/array-mix.js new file mode 100644 index 0000000..90f82cb --- /dev/null +++ b/packages/bundle-utils/test/fixtures/codegen/array-mix.js @@ -0,0 +1,18 @@ +export default { + foo: [ + { + foo: 'foo' + }, + [ + 'bar', + [ + { + foo: 'foo' + }, + 'hoge' + ] + ], + 'baz', + ['buz'] + ] +} diff --git a/packages/bundle-utils/test/fixtures/codegen/complex.js b/packages/bundle-utils/test/fixtures/codegen/complex.js new file mode 100644 index 0000000..451a98a --- /dev/null +++ b/packages/bundle-utils/test/fixtures/codegen/complex.js @@ -0,0 +1,28 @@ +const val1 = 1 +export default { + hi: 'hi there!', + nested: { + hello: `hello world!`, + more: { + plural: "@.caml:{'no apples'} | {0} apple | {n} apples" + }, + list: 'hi, {0} !' + }, + template: `hello ${val1} world!`, + こんにちは: 'こんにちは!', + 'single-quote': "I don't know!", + emoji: '😺', + unicode: '\u0041', + 'unicode-escape': '\\u0041', + 'backslash-single-quote': "\\'", + 'backslash-backslash': '\\\\', + errors: ['ERROR1001', 'ERROR1002'], + complex: { + warnings: [ + 'NOTE: This is warning', + { + 'named-waring': 'this is {type} warining' + } + ] + } +} diff --git a/packages/bundle-utils/test/fixtures/codegen/invalid-message.js b/packages/bundle-utils/test/fixtures/codegen/invalid-message.js new file mode 100644 index 0000000..070b7ef --- /dev/null +++ b/packages/bundle-utils/test/fixtures/codegen/invalid-message.js @@ -0,0 +1,11 @@ +export default { + hello: 'こんにちは', + 'this-is-ivalid': '@', + nested: { + array: [ + { + 'this-is-ivalid': '@' + } + ] + } +} diff --git a/packages/bundle-utils/test/fixtures/codegen/simple.js b/packages/bundle-utils/test/fixtures/codegen/simple.js new file mode 100644 index 0000000..a7cceba --- /dev/null +++ b/packages/bundle-utils/test/fixtures/codegen/simple.js @@ -0,0 +1,9 @@ +export default { + hi: 'hi there!', + hello: 'hello world!', + named: `hi, {name} !`, + list: 'hi, {0} !', + literal: "hi, { 'kazupon' } !", + linked: 'hi, @:name !', + plural: "@.caml:{'no apples'} | {0} apple | {n} apples" +} diff --git a/packages/bundle-utils/test/fixtures/codegen/unhanding.js b/packages/bundle-utils/test/fixtures/codegen/unhanding.js new file mode 100644 index 0000000..a6643e6 --- /dev/null +++ b/packages/bundle-utils/test/fixtures/codegen/unhanding.js @@ -0,0 +1,25 @@ +const val1 = 'hello' +export default { + trueValue: true, + falseValue: false, + nullValue: null, + numberValue: 1, + regexValue: /abc/, + funcValue1: function () {}, + funcValue2: () => {}, + identifier: val1, + items: [ + null, + 1, + /abc/, + function () {}, + { + identifier: val1, + nullValue: null, + numberValue: 1, + regexValue: /abc/ + }, + () => {}, + val1 + ] +} diff --git a/packages/bundle-utils/test/generator/__snapshots__/js.test.ts.snap b/packages/bundle-utils/test/generator/__snapshots__/js.test.ts.snap new file mode 100644 index 0000000..b410b32 --- /dev/null +++ b/packages/bundle-utils/test/generator/__snapshots__/js.test.ts.snap @@ -0,0 +1,833 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`array basic: code 1`] = ` +"export default function (Component) { + Component.__i18n = Component.__i18n || [] + Component.__i18n.push({ + \\"locale\\": \\"\\", + \\"resource\\": { + \\"foo\\": [ + [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"bar\\"])};fn.source=\\"bar\\";return fn;})(), + + ], + [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"baz\\"])};fn.source=\\"baz\\";return fn;})(), + + ] + ] + } + }) +} +" +`; + +exports[`array basic: map 1`] = ` +Object { + "mappings": ";2EACQA,SAASC", + "names": Array [ + "bar", + "baz", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "export default { + foo: [['bar'], ['baz']] +} +", + ], + "version": 3, +} +`; + +exports[`array mixed: code 1`] = ` +"export default function (Component) { + Component.__i18n = Component.__i18n || [] + Component.__i18n.push({ + \\"locale\\": \\"\\", + \\"resource\\": { + \\"foo\\": [ + { + \\"foo\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"foo\\"])};fn.source=\\"foo\\";return fn;})() + }, + [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"bar\\"])};fn.source=\\"bar\\";return fn;})(), + [ + { + \\"foo\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"foo\\"])};fn.source=\\"foo\\";return fn;})() + }, + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hoge\\"])};fn.source=\\"hoge\\";return fn;})(), + + ] + ], + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"baz\\"])};fn.source=\\"baz\\";return fn;})(), + [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"buz\\"])};fn.source=\\"buz\\";return fn;})(), + + ] + ] + } + }) +} +" +`; + +exports[`array mixed: map 1`] = ` +Object { + "mappings": ";;;6EAGUA;;;wEAGLC;;;iFAGSD;;0EAEPE;;;sEAGJC;uEACCC", + "names": Array [ + "foo", + "bar", + "hoge", + "baz", + "buz", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "export default { + foo: [ + { + foo: 'foo' + }, + [ + 'bar', + [ + { + foo: 'foo' + }, + 'hoge' + ] + ], + 'baz', + ['buz'] + ] +} +", + ], + "version": 3, +} +`; + +exports[`bare: code 1`] = ` +"{ + \\"hello\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})() +}" +`; + +exports[`bare: map 1`] = ` +Object { + "mappings": ";2EACQA", + "names": Array [ + "hello world!", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "export default { + hello: 'hello world!' +} +", + ], + "version": 3, +} +`; + +exports[`bridge with ESM exporting: code 1`] = ` +"export default function (Component) { + Component.options.__i18n = Component.options.__i18n || [] + Component.options.__i18n.push({ + \\"locale\\": \\"\\", + \\"resource\\": { + \\"hi\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hi there!\\"])};fn.source=\\"hi there!\\";return fn;})(), + \\"nested\\": { + \\"hello\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"more\\": { + \\"plural\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, linked: _linked, interpolate: _interpolate, list: _list, named: _named, plural: _plural } = ctx;return _plural([_normalize([_linked(\\"no apples\\", \\"caml\\")]), _normalize([_interpolate(_list(0)), \\" apple\\"]), _normalize([_interpolate(_named(\\"n\\")), \\" apples\\"])])};fn.source=\\"@.caml:{'no apples'} | {0} apple | {n} apples\\";return fn;})() + }, + \\"list\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, list: _list } = ctx;return _normalize([\\"hi, \\", _interpolate(_list(0)), \\" !\\"])};fn.source=\\"hi, {0} !\\";return fn;})() + }, + \\"template\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"こんにちは\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"こんにちは!\\"])};fn.source=\\"こんにちは!\\";return fn;})(), + \\"single-quote\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"I don't know!\\"])};fn.source=\\"I don't know!\\";return fn;})(), + \\"emoji\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"😺\\"])};fn.source=\\"😺\\";return fn;})(), + \\"unicode\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"A\\"])};fn.source=\\"A\\";return fn;})(), + \\"unicode-escape\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\u0041\\"])};fn.source=\\"\\\\\\\\u0041\\";return fn;})(), + \\"backslash-single-quote\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\'\\"])};fn.source=\\"\\\\\\\\'\\";return fn;})(), + \\"backslash-backslash\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\\\\\\\\\\\"])};fn.source=\\"\\\\\\\\\\\\\\\\\\";return fn;})(), + \\"errors\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"ERROR1001\\"])};fn.source=\\"ERROR1001\\";return fn;})(), + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"ERROR1002\\"])};fn.source=\\"ERROR1002\\";return fn;})(), + + ], + \\"complex\\": { + \\"warnings\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"NOTE: This is warning\\"])};fn.source=\\"NOTE: This is warning\\";return fn;})(), + { + \\"named-waring\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, named: _named } = ctx;return _normalize([\\"this is \\", _interpolate(_named(\\"type\\")), \\" warining\\"])};fn.source=\\"this is {type} warining\\";return fn;})() + } + ] + } + } + }) + Component.options.__i18nBridge = Component.options.__i18nBridge || [] + Component.options.__i18nBridge.push('\\"{\\\\\\\\n \\\\\\\\\\"hi\\\\\\\\\\": \\\\\\\\\\"hi there!\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"nested\\\\\\\\\\": {\\\\\\\\n \\\\\\\\\\"hello\\\\\\\\\\": \\\\\\\\\\"hello world!\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"more\\\\\\\\\\": {\\\\\\\\n \\\\\\\\\\"plural\\\\\\\\\\": \\\\\\\\\\"@.caml:{\\\\u0027no apples\\\\u0027} | {0} apple | {n} apples\\\\\\\\\\"\\\\\\\\n },\\\\\\\\n \\\\\\\\\\"list\\\\\\\\\\": \\\\\\\\\\"hi, {0} !\\\\\\\\\\"\\\\\\\\n },\\\\\\\\n \\\\\\\\\\"こんにちは\\\\\\\\\\": \\\\\\\\\\"こんにちは!\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"single-quote\\\\\\\\\\": \\\\\\\\\\"I don\\\\u0027t know!\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"emoji\\\\\\\\\\": \\\\\\\\\\"😺\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"unicode\\\\\\\\\\": \\\\\\\\\\"\\\\\\\\\\\\\\\\u0041\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"unicode-escape\\\\\\\\\\": \\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\u0041\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"backslash-single-quote\\\\\\\\\\": \\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\u0027\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"backslash-backslash\\\\\\\\\\": \\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"errors\\\\\\\\\\": [\\\\\\\\\\"ERROR1001\\\\\\\\\\", \\\\\\\\\\"ERROR1002\\\\\\\\\\"],\\\\\\\\n \\\\\\\\\\"complex\\\\\\\\\\": {\\\\\\\\n \\\\\\\\\\"warnings\\\\\\\\\\": [\\\\\\\\n \\\\\\\\\\"NOTE: This is warning\\\\\\\\\\",\\\\\\\\n {\\\\\\\\n \\\\\\\\\\"named-waring\\\\\\\\\\": \\\\\\\\\\"this is {type} warining\\\\\\\\\\"\\\\\\\\n }\\\\\\\\n ]\\\\\\\\n }\\\\\\\\n}\\\\\\\\n\\"') + delete Component.options._Ctor + +} +" +`; + +exports[`bridge with ESM exporting: map 1`] = ` +Object { + "mappings": ";;wEAEKA;;6EAEKC;;0LAEGC,aAAAC,uBAAAC,wBAAAC,wBAAAC,2BAAAC;;oHAEJC,QAAAJ,wBAAAK;;8EAEEC;2EACHC;oFACSC;2EACTC;6EACEC;sFACSC;8FACQC;2FACHC;6EACdC,aAAaC;;;wEAGlBC;;oIAEkBC,YAAAC,8BAAAC", + "names": Array [ + "hi there!", + "hello world!", + "no apples", + "caml", + "0", + " apple", + "n", + " apples", + "hi, ", + " !", + "hello world!", + "こんにちは!", + "I don't know!", + "😺", + "A", + "\\\\u0041", + "\\\\'", + "\\\\\\\\", + "ERROR1001", + "ERROR1002", + "NOTE: This is warning", + "this is ", + "type", + " warining", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "const val1 = 1 +export default { + hi: 'hi there!', + nested: { + hello: \`hello world!\`, + more: { + plural: \\"@.caml:{'no apples'} | {0} apple | {n} apples\\" + }, + list: 'hi, {0} !' + }, + template: \`hello \${val1} world!\`, + こんにちは: 'こんにちは!', + 'single-quote': \\"I don't know!\\", + emoji: '😺', + unicode: '\\\\u0041', + 'unicode-escape': '\\\\\\\\u0041', + 'backslash-single-quote': \\"\\\\\\\\'\\", + 'backslash-backslash': '\\\\\\\\\\\\\\\\', + errors: ['ERROR1001', 'ERROR1002'], + complex: { + warnings: [ + 'NOTE: This is warning', + { + 'named-waring': 'this is {type} warining' + } + ] + } +} +", + ], + "version": 3, +} +`; + +exports[`bridge: code 1`] = ` +"module.exports = function (Component) { + Component.options.__i18n = Component.options.__i18n || [] + Component.options.__i18n.push({ + \\"locale\\": \\"\\", + \\"resource\\": { + \\"hi\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hi there!\\"])};fn.source=\\"hi there!\\";return fn;})(), + \\"nested\\": { + \\"hello\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"more\\": { + \\"plural\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, linked: _linked, interpolate: _interpolate, list: _list, named: _named, plural: _plural } = ctx;return _plural([_normalize([_linked(\\"no apples\\", \\"caml\\")]), _normalize([_interpolate(_list(0)), \\" apple\\"]), _normalize([_interpolate(_named(\\"n\\")), \\" apples\\"])])};fn.source=\\"@.caml:{'no apples'} | {0} apple | {n} apples\\";return fn;})() + }, + \\"list\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, list: _list } = ctx;return _normalize([\\"hi, \\", _interpolate(_list(0)), \\" !\\"])};fn.source=\\"hi, {0} !\\";return fn;})() + }, + \\"template\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"こんにちは\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"こんにちは!\\"])};fn.source=\\"こんにちは!\\";return fn;})(), + \\"single-quote\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"I don't know!\\"])};fn.source=\\"I don't know!\\";return fn;})(), + \\"emoji\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"😺\\"])};fn.source=\\"😺\\";return fn;})(), + \\"unicode\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"A\\"])};fn.source=\\"A\\";return fn;})(), + \\"unicode-escape\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\u0041\\"])};fn.source=\\"\\\\\\\\u0041\\";return fn;})(), + \\"backslash-single-quote\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\'\\"])};fn.source=\\"\\\\\\\\'\\";return fn;})(), + \\"backslash-backslash\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\\\\\\\\\\\"])};fn.source=\\"\\\\\\\\\\\\\\\\\\";return fn;})(), + \\"errors\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"ERROR1001\\"])};fn.source=\\"ERROR1001\\";return fn;})(), + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"ERROR1002\\"])};fn.source=\\"ERROR1002\\";return fn;})(), + + ], + \\"complex\\": { + \\"warnings\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"NOTE: This is warning\\"])};fn.source=\\"NOTE: This is warning\\";return fn;})(), + { + \\"named-waring\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, named: _named } = ctx;return _normalize([\\"this is \\", _interpolate(_named(\\"type\\")), \\" warining\\"])};fn.source=\\"this is {type} warining\\";return fn;})() + } + ] + } + } + }) + Component.options.__i18nBridge = Component.options.__i18nBridge || [] + Component.options.__i18nBridge.push('\\"{\\\\\\\\n \\\\\\\\\\"hi\\\\\\\\\\": \\\\\\\\\\"hi there!\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"nested\\\\\\\\\\": {\\\\\\\\n \\\\\\\\\\"hello\\\\\\\\\\": \\\\\\\\\\"hello world!\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"more\\\\\\\\\\": {\\\\\\\\n \\\\\\\\\\"plural\\\\\\\\\\": \\\\\\\\\\"@.caml:{\\\\u0027no apples\\\\u0027} | {0} apple | {n} apples\\\\\\\\\\"\\\\\\\\n },\\\\\\\\n \\\\\\\\\\"list\\\\\\\\\\": \\\\\\\\\\"hi, {0} !\\\\\\\\\\"\\\\\\\\n },\\\\\\\\n \\\\\\\\\\"こんにちは\\\\\\\\\\": \\\\\\\\\\"こんにちは!\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"single-quote\\\\\\\\\\": \\\\\\\\\\"I don\\\\u0027t know!\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"emoji\\\\\\\\\\": \\\\\\\\\\"😺\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"unicode\\\\\\\\\\": \\\\\\\\\\"\\\\\\\\\\\\\\\\u0041\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"unicode-escape\\\\\\\\\\": \\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\u0041\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"backslash-single-quote\\\\\\\\\\": \\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\u0027\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"backslash-backslash\\\\\\\\\\": \\\\\\\\\\"\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\",\\\\\\\\n \\\\\\\\\\"errors\\\\\\\\\\": [\\\\\\\\\\"ERROR1001\\\\\\\\\\", \\\\\\\\\\"ERROR1002\\\\\\\\\\"],\\\\\\\\n \\\\\\\\\\"complex\\\\\\\\\\": {\\\\\\\\n \\\\\\\\\\"warnings\\\\\\\\\\": [\\\\\\\\n \\\\\\\\\\"NOTE: This is warning\\\\\\\\\\",\\\\\\\\n {\\\\\\\\n \\\\\\\\\\"named-waring\\\\\\\\\\": \\\\\\\\\\"this is {type} warining\\\\\\\\\\"\\\\\\\\n }\\\\\\\\n ]\\\\\\\\n }\\\\\\\\n}\\\\\\\\n\\"') + delete Component.options._Ctor + +} +" +`; + +exports[`bridge: map 1`] = ` +Object { + "mappings": ";;wEAEKA;;6EAEKC;;0LAEGC,aAAAC,uBAAAC,wBAAAC,wBAAAC,2BAAAC;;oHAEJC,QAAAJ,wBAAAK;;8EAEEC;2EACHC;oFACSC;2EACTC;6EACEC;sFACSC;8FACQC;2FACHC;6EACdC,aAAaC;;;wEAGlBC;;oIAEkBC,YAAAC,8BAAAC", + "names": Array [ + "hi there!", + "hello world!", + "no apples", + "caml", + "0", + " apple", + "n", + " apples", + "hi, ", + " !", + "hello world!", + "こんにちは!", + "I don't know!", + "😺", + "A", + "\\\\u0041", + "\\\\'", + "\\\\\\\\", + "ERROR1001", + "ERROR1002", + "NOTE: This is warning", + "this is ", + "type", + " warining", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "const val1 = 1 +export default { + hi: 'hi there!', + nested: { + hello: \`hello world!\`, + more: { + plural: \\"@.caml:{'no apples'} | {0} apple | {n} apples\\" + }, + list: 'hi, {0} !' + }, + template: \`hello \${val1} world!\`, + こんにちは: 'こんにちは!', + 'single-quote': \\"I don't know!\\", + emoji: '😺', + unicode: '\\\\u0041', + 'unicode-escape': '\\\\\\\\u0041', + 'backslash-single-quote': \\"\\\\\\\\'\\", + 'backslash-backslash': '\\\\\\\\\\\\\\\\', + errors: ['ERROR1001', 'ERROR1002'], + complex: { + warnings: [ + 'NOTE: This is warning', + { + 'named-waring': 'this is {type} warining' + } + ] + } +} +", + ], + "version": 3, +} +`; + +exports[`complex: code 1`] = ` +"export default { + \\"hi\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hi there!\\"])};fn.source=\\"hi there!\\";return fn;})(), + \\"nested\\": { + \\"hello\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"more\\": { + \\"plural\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, linked: _linked, interpolate: _interpolate, list: _list, named: _named, plural: _plural } = ctx;return _plural([_normalize([_linked(\\"no apples\\", \\"caml\\")]), _normalize([_interpolate(_list(0)), \\" apple\\"]), _normalize([_interpolate(_named(\\"n\\")), \\" apples\\"])])};fn.source=\\"@.caml:{'no apples'} | {0} apple | {n} apples\\";return fn;})() + }, + \\"list\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, list: _list } = ctx;return _normalize([\\"hi, \\", _interpolate(_list(0)), \\" !\\"])};fn.source=\\"hi, {0} !\\";return fn;})() + }, + \\"template\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"こんにちは\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"こんにちは!\\"])};fn.source=\\"こんにちは!\\";return fn;})(), + \\"single-quote\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"I don't know!\\"])};fn.source=\\"I don't know!\\";return fn;})(), + \\"emoji\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"😺\\"])};fn.source=\\"😺\\";return fn;})(), + \\"unicode\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"A\\"])};fn.source=\\"A\\";return fn;})(), + \\"unicode-escape\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\u0041\\"])};fn.source=\\"\\\\\\\\u0041\\";return fn;})(), + \\"backslash-single-quote\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\'\\"])};fn.source=\\"\\\\\\\\'\\";return fn;})(), + \\"backslash-backslash\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\\\\\\\\\\\"])};fn.source=\\"\\\\\\\\\\\\\\\\\\";return fn;})(), + \\"errors\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"ERROR1001\\"])};fn.source=\\"ERROR1001\\";return fn;})(), + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"ERROR1002\\"])};fn.source=\\"ERROR1002\\";return fn;})(), + + ], + \\"complex\\": { + \\"warnings\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"NOTE: This is warning\\"])};fn.source=\\"NOTE: This is warning\\";return fn;})(), + { + \\"named-waring\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, named: _named } = ctx;return _normalize([\\"this is \\", _interpolate(_named(\\"type\\")), \\" warining\\"])};fn.source=\\"this is {type} warining\\";return fn;})() + } + ] + } +}" +`; + +exports[`complex: map 1`] = ` +Object { + "mappings": ";;wEAEKA;;6EAEKC;;0LAEGC,aAAAC,uBAAAC,wBAAAC,wBAAAC,2BAAAC;;oHAEJC,QAAAJ,wBAAAK;;8EAEEC;2EACHC;oFACSC;2EACTC;6EACEC;sFACSC;8FACQC;2FACHC;6EACdC,aAAaC;;;wEAGlBC;;oIAEkBC,YAAAC,8BAAAC", + "names": Array [ + "hi there!", + "hello world!", + "no apples", + "caml", + "0", + " apple", + "n", + " apples", + "hi, ", + " !", + "hello world!", + "こんにちは!", + "I don't know!", + "😺", + "A", + "\\\\u0041", + "\\\\'", + "\\\\\\\\", + "ERROR1001", + "ERROR1002", + "NOTE: This is warning", + "this is ", + "type", + " warining", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "const val1 = 1 +export default { + hi: 'hi there!', + nested: { + hello: \`hello world!\`, + more: { + plural: \\"@.caml:{'no apples'} | {0} apple | {n} apples\\" + }, + list: 'hi, {0} !' + }, + template: \`hello \${val1} world!\`, + こんにちは: 'こんにちは!', + 'single-quote': \\"I don't know!\\", + emoji: '😺', + unicode: '\\\\u0041', + 'unicode-escape': '\\\\\\\\u0041', + 'backslash-single-quote': \\"\\\\\\\\'\\", + 'backslash-backslash': '\\\\\\\\\\\\\\\\', + errors: ['ERROR1001', 'ERROR1002'], + complex: { + warnings: [ + 'NOTE: This is warning', + { + 'named-waring': 'this is {type} warining' + } + ] + } +} +", + ], + "version": 3, +} +`; + +exports[`force stringify: code 1`] = ` +"export default { + \\"trueValue\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"true\\"])};fn.source=\\"true\\";return fn;})(), + \\"falseValue\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"false\\"])};fn.source=\\"false\\";return fn;})(), + \\"nullValue\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"null\\"])};fn.source=\\"null\\";return fn;})(), + \\"numberValue\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"1\\"])};fn.source=\\"1\\";return fn;})(), + \\"items\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"null\\"])};fn.source=\\"null\\";return fn;})(), + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"1\\"])};fn.source=\\"1\\";return fn;})(), + { + \\"nullValue\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"null\\"])};fn.source=\\"null\\";return fn;})(), + \\"numberValue\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"1\\"])};fn.source=\\"1\\";return fn;})(), + + }, + + ] +}" +`; + +exports[`force stringify: map 1`] = ` +Object { + "mappings": ";;+EAEYA;gFACCC;+EACDC;iFACEC;;;;;;sEAMXD;sEACAC;;;;;mFAKaD;qFACEC", + "names": Array [ + "true", + "false", + "null", + "1", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "const val1 = 'hello' +export default { + trueValue: true, + falseValue: false, + nullValue: null, + numberValue: 1, + regexValue: /abc/, + funcValue1: function () {}, + funcValue2: () => {}, + identifier: val1, + items: [ + null, + 1, + /abc/, + function () {}, + { + identifier: val1, + nullValue: null, + numberValue: 1, + regexValue: /abc/ + }, + () => {}, + val1 + ] +} +", + ], + "version": 3, +} +`; + +exports[`invalid message syntax: code 1`] = ` +"{ + \\"hello\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"こんにちは\\"])};fn.source=\\"こんにちは\\";return fn;})(), + \\"this-is-ivalid\\": @, + \\"nested\\": { + \\"array\\": [ + { + \\"this-is-ivalid\\": @ + } + ] + } +}" +`; + +exports[`invalid message syntax: errors 1`] = ` +Array [ + Object { + "code": 14, + "domain": "parser", + "location": Object { + "end": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + "start": Object { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "msg": "Unexpected lexical analysis in token: 'EOF'", + "path": "this-is-ivalid", + "source": "@", + }, + Object { + "code": 13, + "domain": "parser", + "location": Object { + "end": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + "start": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + }, + "msg": "Unexpected empty linked key", + "path": "this-is-ivalid", + "source": "@", + }, + Object { + "code": 14, + "domain": "parser", + "location": Object { + "end": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + "start": Object { + "column": 1, + "line": 1, + "offset": 0, + }, + }, + "msg": "Unexpected lexical analysis in token: 'EOF'", + "path": "nested.array.0.this-is-ivalid", + "source": "@", + }, + Object { + "code": 13, + "domain": "parser", + "location": Object { + "end": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + "start": Object { + "column": 2, + "line": 1, + "offset": 1, + }, + }, + "msg": "Unexpected empty linked key", + "path": "nested.array.0.this-is-ivalid", + "source": "@", + }, +] +`; + +exports[`invalid message syntax: map 1`] = ` +Object { + "mappings": ";2EACQA;+GACWC;;;;qHAIMA", + "names": Array [ + "こんにちは", + "", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "export default { + hello: 'こんにちは', + 'this-is-ivalid': '@', + nested: { + array: [ + { + 'this-is-ivalid': '@' + } + ] + } +} +", + ], + "version": 3, +} +`; + +exports[`simple: code 1`] = ` +"export default { + \\"hi\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hi there!\\"])};fn.source=\\"hi there!\\";return fn;})(), + \\"hello\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"named\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, named: _named } = ctx;return _normalize([\\"hi, \\", _interpolate(_named(\\"name\\")), \\" !\\"])};fn.source=\\"hi, {name} !\\";return fn;})(), + \\"list\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, list: _list } = ctx;return _normalize([\\"hi, \\", _interpolate(_list(0)), \\" !\\"])};fn.source=\\"hi, {0} !\\";return fn;})(), + \\"literal\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hi, \\", \\"kazupon\\", \\" !\\"])};fn.source=\\"hi, { 'kazupon' } !\\";return fn;})(), + \\"linked\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, linked: _linked } = ctx;return _normalize([\\"hi, \\", _linked(\\"name\\"), \\" !\\"])};fn.source=\\"hi, @:name !\\";return fn;})(), + \\"plural\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, linked: _linked, interpolate: _interpolate, list: _list, named: _named, plural: _plural } = ctx;return _plural([_normalize([_linked(\\"no apples\\", \\"caml\\")]), _normalize([_interpolate(_list(0)), \\" apple\\"]), _normalize([_interpolate(_named(\\"n\\")), \\" apples\\"])])};fn.source=\\"@.caml:{'no apples'} | {0} apple | {n} apples\\";return fn;})() +}" +`; + +exports[`simple: map 1`] = ` +Object { + "mappings": ";wEACKA;2EACGC;qHACAC,QAAAC,8BAAAC;kHACDF,QAAAG,wBAAAD;6EACGF,QAAAI,WAAAF;6FACDF,gBAAAC,SAAAC;sLACAG,aAAAC,uBAAAH,wBAAAI,wBAAAC,2BAAAC", + "names": Array [ + "hi there!", + "hello world!", + "hi, ", + "name", + " !", + "0", + "kazupon", + "no apples", + "caml", + " apple", + "n", + " apples", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "export default { + hi: 'hi there!', + hello: 'hello world!', + named: \`hi, {name} !\`, + list: 'hi, {0} !', + literal: \\"hi, { 'kazupon' } !\\", + linked: 'hi, @:name !', + plural: \\"@.caml:{'no apples'} | {0} apple | {n} apples\\" +} +", + ], + "version": 3, +} +`; + +exports[`unhandling: code 1`] = ` +"export default { + \\"trueValue\\": true, + \\"falseValue\\": false, + \\"nullValue\\": null, + \\"numberValue\\": 1, + \\"items\\": [ + null, + 1, + { + \\"nullValue\\": null, + \\"numberValue\\": 1, + + }, + + ] +}" +`; + +exports[`unhandling: map 1`] = ` +Object { + "mappings": "", + "names": Array [], + "sources": Array [], + "version": 3, +} +`; + +exports[`useClassComponent: code 1`] = ` +"export default function (Component) { + Component.__o.__i18n = Component.__o.__i18n || [] + Component.__o.__i18n.push({ + \\"locale\\": \\"\\", + \\"resource\\": { + \\"hi\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hi there!\\"])};fn.source=\\"hi there!\\";return fn;})(), + \\"nested\\": { + \\"hello\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"more\\": { + \\"plural\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, linked: _linked, interpolate: _interpolate, list: _list, named: _named, plural: _plural } = ctx;return _plural([_normalize([_linked(\\"no apples\\", \\"caml\\")]), _normalize([_interpolate(_list(0)), \\" apple\\"]), _normalize([_interpolate(_named(\\"n\\")), \\" apples\\"])])};fn.source=\\"@.caml:{'no apples'} | {0} apple | {n} apples\\";return fn;})() + }, + \\"list\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, list: _list } = ctx;return _normalize([\\"hi, \\", _interpolate(_list(0)), \\" !\\"])};fn.source=\\"hi, {0} !\\";return fn;})() + }, + \\"template\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"hello world!\\"])};fn.source=\\"hello world!\\";return fn;})(), + \\"こんにちは\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"こんにちは!\\"])};fn.source=\\"こんにちは!\\";return fn;})(), + \\"single-quote\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"I don't know!\\"])};fn.source=\\"I don't know!\\";return fn;})(), + \\"emoji\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"😺\\"])};fn.source=\\"😺\\";return fn;})(), + \\"unicode\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"A\\"])};fn.source=\\"A\\";return fn;})(), + \\"unicode-escape\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\u0041\\"])};fn.source=\\"\\\\\\\\u0041\\";return fn;})(), + \\"backslash-single-quote\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\'\\"])};fn.source=\\"\\\\\\\\'\\";return fn;})(), + \\"backslash-backslash\\": (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"\\\\\\\\\\\\\\\\\\"])};fn.source=\\"\\\\\\\\\\\\\\\\\\";return fn;})(), + \\"errors\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"ERROR1001\\"])};fn.source=\\"ERROR1001\\";return fn;})(), + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"ERROR1002\\"])};fn.source=\\"ERROR1002\\";return fn;})(), + + ], + \\"complex\\": { + \\"warnings\\": [ + (()=>{const fn=(ctx) => {const { normalize: _normalize } = ctx;return _normalize([\\"NOTE: This is warning\\"])};fn.source=\\"NOTE: This is warning\\";return fn;})(), + { + \\"named-waring\\": (()=>{const fn=(ctx) => {const { normalize: _normalize, interpolate: _interpolate, named: _named } = ctx;return _normalize([\\"this is \\", _interpolate(_named(\\"type\\")), \\" warining\\"])};fn.source=\\"this is {type} warining\\";return fn;})() + } + ] + } + } + }) +} +" +`; + +exports[`useClassComponent: map 1`] = ` +Object { + "mappings": ";;wEAEKA;;6EAEKC;;0LAEGC,aAAAC,uBAAAC,wBAAAC,wBAAAC,2BAAAC;;oHAEJC,QAAAJ,wBAAAK;;8EAEEC;2EACHC;oFACSC;2EACTC;6EACEC;sFACSC;8FACQC;2FACHC;6EACdC,aAAaC;;;wEAGlBC;;oIAEkBC,YAAAC,8BAAAC", + "names": Array [ + "hi there!", + "hello world!", + "no apples", + "caml", + "0", + " apple", + "n", + " apples", + "hi, ", + " !", + "hello world!", + "こんにちは!", + "I don't know!", + "😺", + "A", + "\\\\u0041", + "\\\\'", + "\\\\\\\\", + "ERROR1001", + "ERROR1002", + "NOTE: This is warning", + "this is ", + "type", + " warining", + ], + "sources": Array [ + "vue-i18n-loader.js", + ], + "sourcesContent": Array [ + "const val1 = 1 +export default { + hi: 'hi there!', + nested: { + hello: \`hello world!\`, + more: { + plural: \\"@.caml:{'no apples'} | {0} apple | {n} apples\\" + }, + list: 'hi, {0} !' + }, + template: \`hello \${val1} world!\`, + こんにちは: 'こんにちは!', + 'single-quote': \\"I don't know!\\", + emoji: '😺', + unicode: '\\\\u0041', + 'unicode-escape': '\\\\\\\\u0041', + 'backslash-single-quote': \\"\\\\\\\\'\\", + 'backslash-backslash': '\\\\\\\\\\\\\\\\', + errors: ['ERROR1001', 'ERROR1002'], + complex: { + warnings: [ + 'NOTE: This is warning', + { + 'named-waring': 'this is {type} warining' + } + ] + } +} +", + ], + "version": 3, +} +`; diff --git a/packages/bundle-utils/test/generator/js.test.ts b/packages/bundle-utils/test/generator/js.test.ts new file mode 100644 index 0000000..2bc91c2 --- /dev/null +++ b/packages/bundle-utils/test/generator/js.test.ts @@ -0,0 +1,200 @@ +import { readFile } from '../utils' +import { generate } from '../../src/js' +import { parse } from '@babel/parser' + +function validateSyntax(code: string): boolean { + let ret = false + try { + const node = parse(code, { + sourceType: 'module' + }) + ret = !node.errors.length + } catch (e) { + console.log(`invalid sytanx on \n${code}`) + console.error(e) + } + return ret +} + +test('simple', async () => { + const { source } = await readFile('./fixtures/codegen/simple.js') + const { code, map } = generate(source, { + sourceMap: true, + env: 'development' + }) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('unhandling', async () => { + const { source } = await readFile('./fixtures/codegen/unhanding.js') + const { code, map } = generate(source, { + sourceMap: true, + env: 'development' + }) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('force stringify', async () => { + const { source } = await readFile('./fixtures/codegen/unhanding.js') + const { code, map } = generate(source, { + sourceMap: true, + env: 'development', + forceStringify: true + }) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('complex', async () => { + const { source } = await readFile('./fixtures/codegen/complex.js') + const { code, map } = generate(source, { + sourceMap: true, + env: 'development' + }) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('bare', async () => { + const { source } = await readFile('./fixtures/bare.js') + const { code, map } = generate(source, { + type: 'bare', + sourceMap: true, + env: 'development' + }) + + expect(validateSyntax(`export default \n${code}`)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('bridge', async () => { + const { source } = await readFile('./fixtures/codegen/complex.js') + const { source: json } = await readFile('./fixtures/codegen/complex.json') + const { code, map } = generate( + source, + { + type: 'sfc', + bridge: true, + sourceMap: true, + env: 'development' + }, + () => { + return JSON.stringify(json) + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029') + .replace(/\\/g, '\\\\') + .replace(/\u0027/g, '\\u0027') + } + ) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('bridge with ESM exporting', async () => { + const { source } = await readFile('./fixtures/codegen/complex.js') + const { source: json } = await readFile('./fixtures/codegen/complex.json') + const { code, map } = generate( + source, + { + type: 'sfc', + bridge: true, + exportESM: true, + sourceMap: true, + env: 'development' + }, + () => { + return JSON.stringify(json) + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029') + .replace(/\\/g, '\\\\') + .replace(/\u0027/g, '\\u0027') + } + ) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('useClassComponent', async () => { + const { source } = await readFile('./fixtures/codegen/complex.js') + const { code, map } = generate( + source, + { + type: 'sfc', + useClassComponent: true, + sourceMap: true, + env: 'development' + }, + () => { + return source + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029') + .replace(/\\/g, '\\\\') + .replace(/\u0027/g, '\\u0027') + } + ) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(code).toContain('Component.__o.__i18n') + expect(code).not.toContain('Component.__i18n') + expect(map).toMatchSnapshot('map') +}) + +test('array basic', async () => { + const { source } = await readFile('./fixtures/codegen/array-basic.js') + const { code, map } = generate(source, { + type: 'sfc', + sourceMap: true, + env: 'development' + }) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('array mixed', async () => { + const { source } = await readFile('./fixtures/codegen/array-mix.js') + const { code, map } = generate(source, { + type: 'sfc', + sourceMap: true, + env: 'development' + }) + + expect(validateSyntax(code)).toBe(true) + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) + +test('invalid message syntax', async () => { + const { source } = await readFile('./fixtures/codegen/invalid-message.js') + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const errors = [] as any + const { code, map } = generate(source, { + type: 'bare', + sourceMap: true, + env: 'development', + onError(msg, extra) { + errors.push(Object.assign({ msg }, extra || {})) + } + }) + + expect(errors).toMatchSnapshot('errors') + expect(code).toMatchSnapshot('code') + expect(map).toMatchSnapshot('map') +}) diff --git a/yarn.lock b/yarn.lock index b8edf73..7d3d1ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -172,6 +172,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.21.1": + version: 7.21.1 + resolution: "@babel/generator@npm:7.21.1" + dependencies: + "@babel/types": ^7.21.0 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 69085a211ff91a7a608ee3f86e6fcb9cf5e724b756d792a713b0c328a671cd3e423e1ef1b12533f366baba0616caffe0a7ba9d328727eab484de5961badbef00 + languageName: node + linkType: hard + "@babel/helper-compilation-targets@npm:^7.17.10": version: 7.17.10 resolution: "@babel/helper-compilation-targets@npm:7.17.10" @@ -250,6 +262,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-function-name@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helper-function-name@npm:7.21.0" + dependencies: + "@babel/template": ^7.20.7 + "@babel/types": ^7.21.0 + checksum: d63e63c3e0e3e8b3138fa47b0cd321148a300ef12b8ee951196994dcd2a492cc708aeda94c2c53759a5c9177fffaac0fd8778791286746f72a000976968daf4e + languageName: node + linkType: hard + "@babel/helper-hoist-variables@npm:^7.16.7": version: 7.16.7 resolution: "@babel/helper-hoist-variables@npm:7.16.7" @@ -510,6 +532,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.2": + version: 7.21.2 + resolution: "@babel/parser@npm:7.21.2" + bin: + parser: ./bin/babel-parser.js + checksum: e2b89de2c63d4cdd2cafeaea34f389bba729727eec7a8728f736bc472a59396059e3e9fe322c9bed8fd126d201fb609712949dc8783f4cae4806acd9a73da6ff + languageName: node + linkType: hard + "@babel/plugin-syntax-async-generators@npm:^7.8.4": version: 7.8.4 resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" @@ -689,6 +720,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/template@npm:7.20.7" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: 2eb1a0ab8d415078776bceb3473d07ab746e6bb4c2f6ca46ee70efb284d75c4a32bb0cd6f4f4946dec9711f9c0780e8e5d64b743208deac6f8e9858afadc349e + languageName: node + linkType: hard + "@babel/traverse@npm:^7.17.10, @babel/traverse@npm:^7.17.3, @babel/traverse@npm:^7.17.9, @babel/traverse@npm:^7.7.0, @babel/traverse@npm:^7.7.2": version: 7.17.10 resolution: "@babel/traverse@npm:7.17.10" @@ -743,6 +785,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.21.2": + version: 7.21.2 + resolution: "@babel/traverse@npm:7.21.2" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/generator": ^7.21.1 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.21.0 + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/parser": ^7.21.2 + "@babel/types": ^7.21.2 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: d851e3f5cfbdc2fac037a014eae7b0707709de50f7d2fbb82ffbf932d3eeba90a77431529371d6e544f8faaf8c6540eeb18fdd8d1c6fa2b61acea0fb47e18d4b + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.16.7, @babel/types@npm:^7.17.0, @babel/types@npm:^7.17.10, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.7.0, @babel/types@npm:^7.8.3": version: 7.17.10 resolution: "@babel/types@npm:7.17.10" @@ -775,6 +835,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.2": + version: 7.21.2 + resolution: "@babel/types@npm:7.21.2" + dependencies: + "@babel/helper-string-parser": ^7.19.4 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: a45a52acde139e575502c6de42c994bdbe262bafcb92ae9381fb54cdf1a3672149086843fda655c7683ce9806e998fd002bbe878fa44984498d0fdc7935ce7ff + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -973,6 +1044,9 @@ __metadata: version: 0.0.0-use.local resolution: "@intlify/bundle-utils@workspace:packages/bundle-utils" dependencies: + "@babel/parser": ^7.21.2 + "@babel/traverse": ^7.21.2 + "@babel/types": ^7.21.2 "@intlify/message-compiler": next "@intlify/shared": next jsonc-eslint-parser: ^1.0.1 @@ -1417,6 +1491,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/resolve-uri@npm:3.1.0": + version: 3.1.0 + resolution: "@jridgewell/resolve-uri@npm:3.1.0" + checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 + languageName: node + linkType: hard + "@jridgewell/resolve-uri@npm:^3.0.3": version: 3.0.7 resolution: "@jridgewell/resolve-uri@npm:3.0.7" @@ -1438,6 +1519,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.13": + version: 1.4.14 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" + checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 + languageName: node + linkType: hard + "@jridgewell/sourcemap-codec@npm:^1.4.10": version: 1.4.13 resolution: "@jridgewell/sourcemap-codec@npm:1.4.13" @@ -1445,10 +1533,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.13": - version: 1.4.14 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" - checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 +"@jridgewell/trace-mapping@npm:^0.3.17": + version: 0.3.17 + resolution: "@jridgewell/trace-mapping@npm:0.3.17" + dependencies: + "@jridgewell/resolve-uri": 3.1.0 + "@jridgewell/sourcemap-codec": 1.4.14 + checksum: 9d703b859cff5cd83b7308fd457a431387db5db96bd781a63bf48e183418dd9d3d44e76b9e4ae13237f6abeeb25d739ec9215c1d5bfdd08f66f750a50074a339 languageName: node linkType: hard